iOS Debuger(便捷輔助偵錯程式)

WHC發表於2017-01-15

前言

首先寫這篇文章之前祝大家週末愉快,然後自我介紹一下,我叫吳海超(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相關程式碼檔案即可

效果演示

iOS 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…

到了這裡非常感謝您的閱讀謝謝!

相關文章