iOS開發-runtime-黑魔法Method swizzling

記得很久很久以前,啊,其實也沒有很久,有一次面試,面試官問我,你懂runtime么??當時我是一個比現在還菜的菜逼,一句話給我問懵逼了,runtime是啥,我不懂啊。。。

后來面試完回來就趕快去查runtime的資料,至于runtime是什么,這里我就不多說啦,網上無數大牛總結過的東西,我也就不再班門弄斧啦,雖然這次的主題Method Swizzling也有很多大??偨Y,我這篇文章呢,也就茲當做自己學習的筆記啦。

Method Swizzling 是什么

Method Swizzling是objective-c中的黑魔法,算是runtime中的一種實戰使用模式,它允許我們動態的替換方法,實現Hook功能。

但是它也是一把雙刃劍,用得好的人可以用它來很輕松的實現一些復雜的功能,而如果用的不好,后果就真的是毀滅性的傷害,這樣的黑魔法,我們一定要盡力去掌握并駕馭它。

Method Swizzling 能做什么

說了這么多,那我們到底用它來做什么呢??? 先從名字來看,Method方法 Swizzling混合,那他的意思就是方法混合??? 好像也沒有一個準確的翻譯,我們就姑且翻譯成方法交換吧。

也就是說把原來 A方法實現的a,原來B方法實現的b交換一下,讓A來實現b的功能,讓B來實現a的功能。咋一看好像沒什么厲害的地方,不就是交換個方法么,有什么用呢?您先別急,往下看。

Method Swizzling 原理

ok,知道他是什么,能干什么了。接下來我們來看看他的原理吧。

雖然之前我們說Method Swizzling是一把雙刃劍,用不好就容易把自己砍死,但是如果我們知道如何使用,這些就不會那么可怕了。

Method方法中,有兩個關鍵的成員變量:SELIMP。

SEL就是我們平時看到的方法的名稱,比如 @selector(viewWillAppear:)。

IMP是一個函數指針,指向的是方法的實現。

原則上,方法名SELIMP是一一對應的,那Method Swizzling的本質就是改變他們的對應關系,達到交換方法實現的目的。

MethodSwizzlingBefore.png
MethodSwizzlingAfter.png

Method Swizzling實現&實踐

好了,廢話說了一大堆,接下來我們就來說說到底是怎么實現Method Swizzling吧。

我們用一個日常開發中都會用到的需求作為例子,平時我們可能會被產品要求在頁面中添加監聽事件,監聽每個頁面的訪問,那我們需要再每個頁面都添加這樣的代碼:


- (void)viewWillAppear:(BOOL)animated

{

 [super viewWillAppear:animated];

 [TalkingData trackPageBegin:@"P110000"];

}

- (void)viewWillDisappear:(BOOL)animated

{

 [super viewWillDisappear:animated];

 [TalkingData trackPageEnd:@"P110000"];

}

我們可以直接在每個頁面中都添加這樣的代碼,簡單粗暴,但是缺點也很明顯,這樣不僅要干很多體力活兒,還容易漏寫某些頁面,后期可能會被后邊的程序員罵死。

或者我們寫一個UIViewController的基類,讓其他類都繼承他,但是對于已經寫好的代碼,幾十個類這樣修改還是非常麻煩。

這時候就用到了我們的黑魔法Method Swizzling。

Method Swizzling 相關函數介紹

在真正使用黑魔法之前,我們先熟悉一下需要用到的函數:

獲取一個方法名稱(SEL):


class_getInstanceMethod

獲取一個方法的實現(IMP):


method_getImplementation

獲取一個實現編碼類型


method_getTypeEncoding

給方法添加實現


class_addMethod

用一個方法實現替換另一個方法的實現


class_replaceMethod

交換兩個方法的實現


method_exchangeImplementations

Method Swizzling 實現

終于到了實現的部分,好緊臟。

廢話不多說直接上代碼。


+ (void)load

{

 static dispatch_once_t onceToken;

 dispatch_once(&onceToken, ^{

// 獲取類方法

// Class selfClass = object_getClass([self class]);

// 獲取實例方法

 Class selfClass = [self class];

 //獲取原方法的名稱和實現

 SEL oriSEL = @selector(viewWillAppear:);

 Method oriMethod = class_getInstanceMethod(selfClass, oriSEL);

 //獲取替換方法的名稱和實現

 SEL swizzlingSEL = @selector(swizzlingViewWillAppear:);

 Method swizzlingMethod = class_getInstanceMethod(selfClass, swizzlingSEL);

 //給原方法添加替換方法實現,為了避免原方法沒有實現

 BOOL addSucc = class_addMethod(selfClass, oriSEL, method_getImplementation(swizzlingMethod), method_getTypeEncoding(swizzlingMethod));

 //成功,將原方法的實現替換到替換方法的實現

 if (addSucc) {

 class_replaceMethod(selfClass, swizzlingSEL, method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod));

 }

 //失敗,說明原方法已經實現,直接交換方法

 else {

 method_exchangeImplementations(oriMethod, swizzlingMethod);

 }

 });

}

另外附上替換方法的實現,這里我直接打印了類的名稱,具體需要怎么做直接按需求來就行了。


- (void)swizzlingViewWillAppear:(BOOL)animated

{

 NSLog(@"%@",self.class);

 return [self swizzlingViewWillAppear:animated];

}

Method Swizzling 注意要點

功能雖然實現完了,但是這里邊有幾個部分我們要注意一下。

  • 1.為什么要在+load中實現?

  • 2.+load只會被調用一次,為什么還要用dispatch_once?

  • 3.為什么不直接交換方法,而是先要添加方法?

  • 4.在swizzlingViewWillAppear:方法里調用swizzlingViewWillAppear:,不會引起死循環么?

為什么要在+load中實現?

因為+load方法是在類被加載的時候調用的,與之相似的有類的+initialize方法,但是他的一種懶加載模式,當這個類或子類收到第一條消息之前才會調用他,所以+load方法是最好的實現場所。

+load只會被調用一次,為什么還要用dispatch_once?

首先我們要確認我們的Method Swizzling只實現一次,因為多次實現會反復交換方法,導致偶數次調用的實現沒有交換,造成不必要的麻煩,添加dispatch_once算是添加一個雙保險,因為誰知道有沒有人會手動調用+load呢?

為什么不直接交換方法,而是先要添加方法

一般情況下,我們都是為了和我們未知的系統方法添加Method Swizzling,而不是完全替換某個功能,所以我們一般都需要再自定義的實現中調用原始的實現,所以就會出現兩種情況:

1.本身就有實現要替換的方法,這個時候比較簡單,class_addMethod返回NO,我們直接交換方法就好。

2.本身沒有實現要替換的方法,而是繼承了父類的實現,class_addMethod返回YES,這個時候,我們需要使用class_getInstanceMethod函數獲取到原始實現方法指向的方法,也就是父類中的實現,再通過實現class_replaceMethod來實現調用。

swizzlingViewWillAppear:方法里調用swizzlingViewWillAppear:,不會引起死循環么?

因為Method Swizzling的原理為方法互換,所以這時候用執行的swizzlingViewWillAppear:方法,實際上是執行的viewWillAppear:方法,因為我們并不知道OC的viewWillAppear:方法實現了什么內容,所以我們通過這個調用來實現系統的功能。

Demo

如果看到這里了的話,好像也沒有什么必要上Demo了吧??

不過還是放上去好了, Demo地址。

最后

Method Swizzling算是runtime中的一個比較好的實戰使用方式,用好了的話可以解決各種各樣的問題,比如數組越界崩潰等等很多問題,都可以通過他來解決,但是也不能濫用,不小心使用的話,可能真的要半夜爬起來改代碼了。
本文章也僅限個人學習使用,如果有什么寫的不對的地方,還請各位大佬指正批評。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容