首先写这篇文章以前祝你们周末愉快,而后自我介绍一下,我叫吴海超(WHC)在iOS领域有丰富的开发架构经验Github之后我也会以文章的形式分享具备实战意义的文章给你们,但愿可以给你们有所帮助。git
这期我想给你们讲讲iOS中的调式技巧,我想在坐各位都有维护项目的经验,那么咱们在面对一个陌生未知的项目我该如何快速的定位bug文件或者位置呢?好别着急!
我下面就来详细讲解在iOS项目里若是快速定位到相关bug所在VC界面类。程序员
根据我以往开发维护经验来看咱们在面对一个陌生项目定位到相关bug所在VC界面类通常就是添加相关打印和断点试探找出所在界面类,可是咱们添加的打印每每会由于项目的其余打印信息(http接口请求日志信息等等...)所覆盖因此很难一眼看出来,而咱们在可疑相关VC界面类下断点试探这个是可行的可是太耗费时间了而且也会由于处处下断点致使项目出现不少垃圾断点严重影响项目运行和协做开发。github
从上面对传统定位bug分析过程能够看出咱们在面对一个陌生项目要快速准确的定位到相关bug所在VC界面类并不容易,致使企业项目维护成本很高。因此我也一直在思考如何可以快速定位到bug所在VC界面类方法,在我2016年入职《华住》我注意到他们项目状态栏下面有一个用于显示当前App运行接口环境的一个条视图,可是他们只显示了接口地址(主要方便测试人员查看App当前运行接口环境),后来我发现项目文件不少有1800多个文件在我参入修改bug的时候要定位到相关VC界面类很费事(很浪费时间),后来我充分利用了《华住》App状态栏下面的显示接口的barView
,具体效果是怎么样的稍后会演示,先别着急,我利用runtime技术获取当前VC界面类名而后添加显示到状态栏下面barView
上面,果真效果很不错,大大方便了咱们调式解bug速度。数据库
根据我在《华住》工做的经历我在快速调式项目方面进行了总结而从开发一个iOS项目调式辅助器WHC_Debuger并开源分享给在坐各位,但愿能给各位一些启发。swift
一. 能监控并显示当前界面VC的类名到状态栏下面
二. 能实时监控是否有子线程再操做UI行为并给出危险弹窗警告
三. 全部这些监控行为只在项目Debug模式生效(参入编译运行)在咱们发版Release模式将不参入编译安全
四. 无需任何代码来配置或者初始化只须要引入WHC_Debuger相关代码文件便可架构
首先建立一个调试器管理中心WHC_Debuger
WHC_Debuger.h代码以下:async
#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…
也借此机会推荐阅读本人其余优秀开源项目:Github
到了这里很是感谢您的阅读谢谢!