iOS Debuger(便捷辅助调试器)

3,321 阅读6分钟

前言

首先写这篇文章之前祝大家周末愉快,然后自我介绍一下,我叫吴海超(WHC)在iOS领域有丰富的开发架构经验Github以后我也会以文章的形式分享具有实战意义的文章给大家,希望能够给大家有所帮助。

主题

这期我想给大家讲讲iOS中的调式技巧,我想在坐各位都有维护项目的经验,那么我们在面对一个陌生未知的项目我该如何快速的定位bug文件或者位置呢?好别着急!
我下面就来详细讲解在iOS项目里如果快速定位到相关bug所在VC界面类。

传统定位bug分析

根据我以往开发维护经验来看我们在面对一个陌生项目定位到相关bug所在VC界面类一般就是添加相关打印和断点试探找出所在界面类,但是我们添加的打印往往会因为项目的其他打印信息(http接口请求日志信息等等...)所覆盖所以很难一眼看出来,而我们在可疑相关VC界面类下断点试探这个是可行的但是太耗费时间了并且也会因为到处下断点导致项目出现很多垃圾断点严重影响项目运行和协作开发。

传统定位总结

从上面对传统定位bug分析过程可以看出我们在面对一个陌生项目要快速准确的定位到相关bug所在VC界面类并不容易,导致企业项目维护成本很高。所以我也一直在思考如何能够快速定位到bug所在VC界面类方法,在我2016年入职《华住》我注意到他们项目状态栏下面有一个用于显示当前App运行接口环境的一个条视图,但是他们只显示了接口地址(主要方便测试人员查看App当前运行接口环境),后来我发现项目文件很多有1800多个文件在我参入修改bug的时候要定位到相关VC界面类很费事(很浪费时间),后来我充分利用了《华住》App状态栏下面的显示接口的barView,具体效果是怎么样的稍后会演示,先别着急,我利用runtime技术获取当前VC界面类名然后添加显示到状态栏下面barView上面,果然效果很不错,大大方便了我们调式解bug速度。

WHC_Debuger介绍

根据我在《华住》工作的经历我在快速调式项目方面进行了总结而从开发一个iOS项目调式辅助器WHC_Debuger并开源分享给在坐各位,希望能给各位一些启发。

功能介绍

一. 能监控并显示当前界面VC的类名到状态栏下面
二. 能实时监控是否有子线程再操作UI行为并给出危险弹窗警告
三. 所有这些监控行为只在项目Debug模式生效(参入编译运行)在我们发版Release模式将不参入编译

四. 无需任何代码来配置或者初始化只需要引入WHC_Debuger相关代码文件即可

效果演示

WHC_Debuger实现代码分析

首先创建一个调试器管理中心WHC_Debuger
WHC_Debuger.h代码如下:

#import <UIKit/UIKit.h>

#if DEBUG
@interface WHC_Debuger : NSObject

/**
 调试器单利
 @return 调试器
 */
+ (instancetype)share;

/// 自定义要显示的信息
@property (nonatomic, copy)NSString * whc_CustomNote;

/// 显示信息标签
@property (nonatomic, strong, readonly)UILabel * whc_NoteLabel;

@end

#endif

WHC_Debuger.m代码如下:

#if DEBUG
#import "WHC_Debuger.h"

@interface WHC_Debuger ()
@property (nonatomic, strong) UILabel * noteLabel;
@end

@implementation WHC_Debuger

+ (instancetype)share {
    static WHC_Debuger * debuger = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        debuger = WHC_Debuger.new;
    });
    return debuger;
}

- (instancetype)init {
    self = [super init];
    if (self) {
        self.whc_CustomNote = @" 当前控制器:";
    }
    return self;
}

/// 创建VC类名称显示器
- (UILabel *)whc_NoteLabel {
    if (!_noteLabel) {
        CGRect noteLabelFrame;
        noteLabelFrame.origin = CGPointMake(0, 16);
        noteLabelFrame.size = CGSizeMake(CGRectGetWidth(UIScreen.mainScreen.bounds), 20);
        _noteLabel = UILabel.new;
        _noteLabel.frame = noteLabelFrame;
        _noteLabel.textColor = [UIColor colorWithRed:53.0 / 255 green:205.0 / 255 blue:73.0 / 255 alpha:1.0];
        _noteLabel.adjustsFontSizeToFitWidth = YES;
        _noteLabel.minimumScaleFactor = 0.5;
        _noteLabel.font = [UIFont systemFontOfSize:14];
        _noteLabel.backgroundColor = [UIColor colorWithWhite:0 alpha:0.5];
    }
    /// 将VC界面显示器添加到window上面
    if (!_noteLabel.superview) {
        UIWindow * window = [[[UIApplication sharedApplication] delegate] window];
        if (window) {
            [window addSubview:_noteLabel];
        }
    }
    return _noteLabel;
}

@end

#endif

因为我们要监控当前VC界面所以我们要写一个VC的Category
UIViewController+WHC_Debuger.h代码如下:

#if DEBUG
#import <UIKit/UIKit.h>

@interface UIViewController (WHC_Debuger)

@end

#endif

UIViewController+WHC_Debuger.m代码如下:

#if DEBUG
#import "UIViewController+WHC_Debuger.h"
#import "WHC_Debuger.h"
#import <objc/runtime.h>

@implementation UIViewController (WHC_Debuger)

-(void)dealloc {
    NSLog(@">>>>>>>>>>%@ 已经释放了<<<<<<<<<<",[NSStringFromClass(self.class) componentsSeparatedByString:@"."].lastObject);
}

+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
    /// 监控控制器viewWillAppear方法
        Method myViewWillAppear = class_getInstanceMethod(self, @selector(myViewWillAppear:));
        Method viewWillAppear = class_getInstanceMethod(self, @selector(viewWillAppear:));
        method_exchangeImplementations(viewWillAppear, myViewWillAppear);
    });
}

/// 过滤系统内部控制器类
- (BOOL)isPrivateVC {
    NSString * selfClass = NSStringFromClass(self.class);
    return [selfClass isEqualToString:@"UIAlertController"] ||
    [selfClass isEqualToString:@"_UIAlertControllerTextFieldViewController"] ||
    [selfClass isEqualToString:@"UIApplicationRotationFollowingController"] ||
    [selfClass isEqualToString:@"UIInputWindowController"];
}

- (void)myViewWillAppear:(BOOL)animated {
    if (![self isPrivateVC]) {
    /// 获取当前显示的控制器类并显示到barView上面来
        UILabel * noteLabel = WHC_Debuger.share.whc_NoteLabel;
        if (noteLabel.superview) {
            [noteLabel.superview bringSubviewToFront:noteLabel];
        }
        if (WHC_Debuger.share.whc_CustomNote == nil) {
            WHC_Debuger.share.whc_CustomNote = @" ";
        }
        noteLabel.text = [NSString stringWithFormat:@"%@%@",WHC_Debuger.share.whc_CustomNote,[NSStringFromClass(self.class) componentsSeparatedByString:@"."].lastObject];
    }
    [self myViewWillAppear:animated];
}

@end

#endif

实时监控操作UI是否在子线程我们需要写一个UIView的Category
UIView+WHC_Debuger.h代码如下:

#if DEBUG
#import <UIKit/UIKit.h>

@interface UIView (WHC_Debuger)

@end

#endif

UIView+WHC_Debuger.m代码如下:

#if DEBUG
#import "UIView+WHC_Debuger.h"
#import <objc/runtime.h>

@implementation UIView (WHC_Debuger)

+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
    /// 监控UIView的setNeedsDisplay刷新方法
        Method mySetNeedsDisplay = class_getInstanceMethod(self, @selector(mySetNeedsDisplay));
        Method setNeedsDisplay = class_getInstanceMethod(self, @selector(setNeedsDisplay));
        method_exchangeImplementations(setNeedsDisplay, mySetNeedsDisplay);
    });
}

- (void)mySetNeedsDisplay {
    /// 判断当前操作UI的线程是否是主线程如果不是给出危险弹窗提示
    if (NSThread.currentThread != NSThread.mainThread) {
        dispatch_async(dispatch_get_main_queue(), ^{
            NSString * note = [NSStringFromClass(self.class) componentsSeparatedByString:@"."].lastObject;
            NSString * msg = [NSString stringWithFormat:@"%@不在主线程操作UI,危险!!",note];
            UIAlertView * alert = [[UIAlertView alloc] initWithTitle:@"WHC_Debuger" message:msg delegate:nil cancelButtonTitle:@"知道了" otherButtonTitles:nil, nil];
            [alert show];
            NSLog(@">>>>>>>>>%@<<<<<<<<<",msg);
        });
    }
    [self mySetNeedsDisplay];
}

@end

#endif

好了这就是所有WHC_Debuger实现代码,比较简单,但是非常实用。

扩展

我想很多新手可能并不知道为什么我们操作UI要在主线程操作的真正原因,我这里给各位做个知识扩展解释一下这个原因:首先子线程肯定是可以操作UI的前提是你做好了线程安全设置,而我们大多数人都是直接操作没有任何安全配置导致有时候和主线程操作UI冲突导致crash。

这就好比两个足球运动员(一个子线程一个主线程)同时猛力去踢(操作)足球(UI)会发生什么?很显然两败俱伤(crash),那么这种冲突反映到我们项目就是崩溃。所以官方不建议在子线程操作UI,而我们Android为了让我们程序员更老实听话直接在编译器做出了限制(如果检查到子线程操作UI直接报错),很强势。

结束

WHC_Debuger开源地址:github.com/netyouli/WH…

  • 如果您在使用过程中有任何问题,欢迎issue me!
  • 很乐意为您解答任何相关问题!
  • 与其给我点star,不如向我狠狠地抛来一个BUG!
  • 如果您想要更多的接口来自定义或者建议/意见,欢迎issue me!我会根据大家的需求提供更多的接口!

也借此机会推荐阅读本人其他优秀开源项目:Github

  1. 存储高性能模型对象数据库:github.com/netyouli/WH…
  2. Json转Model类Mac工具:github.com/netyouli/WH…
  3. 局部监听VC自动管理键盘:github.com/netyouli/WH…
  4. 扫描iOS/Android项目没有使用图片mac工具:github.com/netyouli/WH…

到了这里非常感谢您的阅读谢谢!