標籤:
我的女神——簡潔實用的iOS代碼調試架構一、引言
這篇部落格的起源是接手了公司的一個已經完成的項目,來做代碼最佳化,項目工程很大,並且引入了很多公司內部的SDK,要搞清楚公司內部的這套架構,的確不是件容易的事,並且由於這個項目是多人開發的,在調試階段會列印出巨量的調試資訊,使得瀏覽有用資訊變的十分困難,更加恐怖的是,很多資訊是SDK中的調試列印,將這些都進行登出是非常費勁甚至不可能的事,於是便有了這樣一些需求:首先,我需要清楚瞭解各個controller之間的跳轉關係,需要快速的弄清每個stroyBoard中各個controller的來龍去脈,其次,我想在不改變其他人的調試代碼的情況下,屏蔽冗餘的log資訊,讓我的調試資料更加清晰明了。於是我想到了如下的解決方案,同樣,如果你有更好的方案或者你知道的優秀的解決辦法,請告知我,十分感謝。
二、追蹤程式的跳轉路徑
這是一個很容易解決的問題,我們都知道,一個controller,如果要展現出來,一定會走生命週期中的viewWillAppear這個方法,我們只需要在這個方法中做些手腳就可以了,實現有兩種思路,一種是採用工廠的設計模式,建立工廠類Controller,在其viewWillAppear中加入我們的調試代碼,但這對於我的項目並不實用,首先我不確定所有controller都會繼承於一個父類,其次,在我沒有找到源頭時,這些類已經在公司的framework中了,我根本沒辦法操作源碼。而第二種方案就是runtime,對的,運行時的OC,沒有不可能。關於runtime的詳細說明,在http://my.oschina.net/u/2340880/blog/489072中有介紹。思路是我們可以寫一個方法,替換掉系統的viewWillAppear,在其中加入我們的調試代碼,這個方法就是Method Swizzing,代碼設計如下:
//建立一個conreoller的類別#import "UIViewController+YHBaseTest.h"#import <objc/runtime.h>@implementation UIViewController (YHBaseTest)+ (void)load { //只執行一次的線程 static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ Class class = [self class]; // When swizzling a class method, use the following: // Class class = object_getClass((id)self); //建立兩個選取器 分別指向 系統的和我們要替換的函數 SEL originalSelector = @selector(viewWillAppear:); SEL swizzledSelector = @selector(YHBaseViewWillAppear:); //擷取方法執行個體 Method originalMethod = class_getInstanceMethod(class, originalSelector); Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector); //現將方法加入 BOOL didAddMethod = class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod)); //進行方法替換 if (didAddMethod) { class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod)); } else { method_exchangeImplementations(originalMethod, swizzledMethod); } });}#pragma mark - Method Swizzling- (void)YHBaseViewWillAppear:(BOOL)animated {//這裡是是我加的一個控制調試鎖 後面會介紹 [self YHBaseViewWillAppear:animated]; YHBaseProcessLog(@"YHBaseTest:ViewWillAppear: %@", self); } 三、屏蔽冗餘的log資訊1、系統的NSLog是個什麼玩意
要戰勝我們的敵人,首先應該瞭解我們的敵人,我們想要屏蔽NSLog的列印,先需要清楚NSLog到底是個什麼玩意。
首先,NSLog的定義如下:
FOUNDATION_EXPORT void NSLog(NSString *format, ...) NS_FORMAT_FUNCTION(1,2);FOUNDATION_EXPORT void NSLogv(NSString *format, va_list args) NS_FORMAT_FUNCTION(1,0);
這裡面有兩個函數,一個是使用多參的格式化字串進行NSLog的列印,一個是通過參數指標進行列印。從這裡我們可以看出,系統的NSLog是一個C風格的函數,所以,我們有思路了,我們可以通過定義一個NSLog宏來替換掉項目中所有的NSLog,如下:
//...是省略參數的宏的寫法,後面的__VA_ARGS__是系統定義好的一個宏,來聲明不定參數#define NSLog(...) YHBaseTestLog(__VA_ARGS__)
2、實現我們自己的NSLog
通過上面的方法,在沒有動源碼的情況下,我們已經可以替換掉程式中所有的列印,可能你會疑問,程式中怎麼會允許我們有兩個NSLog呢,其實這沒什麼神奇的,要知道宏是一種先行編譯的指令,所有這些操作是在代碼編譯之前完成的,實際上程式中已經將NSLog簡單替換成了我們的函數調用,程式中只有一個NSLog,這就是宏的強大之處,狸貓換太子,不錯吧。
下面我們來實現我們的這個函數,如下:
//不要忘了在.h檔案中聲明void YHBaseTestLog(NSString *str,...){//參數列 va_list list; va_start(list, str); //這個地方是一個鎖,後面會介紹 if (![YHBaseTestLock sharedTheSingletion]->_customLock) { //進行列印 NSLogv(str, list); } va_end(list);}
這個函數中其實並沒有做什麼,加了一個鎖的判斷,僅此而已,核心的控制,就交給我們的鎖吧:
//.h檔案@interface YHBaseTestLock : NSObject<YHSingletonProcotol>{ @public BOOL _customLock; BOOL _precessLock;}+(void)customLock;+(void)customUnLock;+(void)processLogLock;+(void)processLogUnLock;//.m//單例方法+(instancetype)sharedTheSingletion{ static YHBaseTestLock * sharedModel = nil; static dispatch_once_t predicate; dispatch_once(&predicate, ^{ sharedModel = [[YHBaseTestLock alloc] init]; }); return sharedModel;}- (instancetype)init{ self = [super init]; if (self) { //初始化 預設使用者的列印都開起 _customLock=NO; //預設 我們加的controller的列印屏蔽 _precessLock=YES; } return self;}//響應的設定+(void)customLock{ [YHBaseTestLock sharedTheSingletion]->_customLock=YES;}+(void)customUnLock{ [YHBaseTestLock sharedTheSingletion]->_customLock=NO;}+(void)processLogLock{ [YHBaseTestLock sharedTheSingletion]->_precessLock=YES;}+(void)processLogUnLock{ [YHBaseTestLock sharedTheSingletion]->_precessLock=NO;}四、看看我們的傑作吧
做完上面的工作後,我們在appdelegate中如如下的簡單配置:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { // Override point for customization after application launch. //將使用者列印加鎖 [YHBaseTestLock customLock]; //將流程列印解鎖 [YHBaseTestLock processLogUnLock]; return YES;}
我們做如下測試:
@interface ViewController ()@end@implementation ViewController- (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view, typically from a nib. NSLog(@"111111111111111111111111111111111111111111111111111111111111111"); [YHBaseTestLock customUnLock]; NSLog(@"這個是有用的資訊:%@==%d",@"看我哦",__LINE__); [YHBaseTestLock customLock]; NSLog(@"32123213123214412312312");}
我在viewController中的有用資訊前後,列印了一些幹擾資訊,並且可以看到,這個NSLog的格式和系統的完全相容,在列印有用資訊前後解鎖和加鎖,結果如下:
可以看到,我們將沒用的列印都屏蔽了,並且列印了程式的跳轉流程。最重要的是,我們對源碼一個字元都沒有修改,同時不會影響與衝突其他人的開發。
這篇部落格開頭,我稱之為我的女神,真心實感,這個方法幫了我很大的忙,在我瞭解項目的結構架構前,每次調試列印都在控制區滾出一片片的資訊著實讓我頭暈腦脹,現在一清涼,精神也清爽不少,^_^。
我的女神——簡潔實用的iOS代碼調試架構