首先寫這篇文章以前祝你們週末愉快,而後自我介紹一下,我叫吳海超(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
到了這裏很是感謝您的閱讀謝謝!