記得很久很久以前,啊,其實也沒有很久,有一次面試,面試官問我,你懂runtime
么??當時我是一個比現在還菜的菜逼,一句話給我問懵逼了,runtime
是啥,我不懂啊。。。
后來面試完回來就趕快去查runtime
的資料,至于runtime
是什么,這里我就不多說啦,網上無數大牛總結過的東西,我也就不再班門弄斧啦,雖然這次的主題Method Swizzling
也有很多大牛總結,我這篇文章呢,也就茲當做自己學習的筆記啦。
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
方法中,有兩個關鍵的成員變量:SEL和IMP。
SEL就是我們平時看到的方法的名稱,比如 @selector(viewWillAppear:)
。
IMP是一個函數指針,指向的是方法的實現。
原則上,方法名SEL和IMP是一一對應的,那Method Swizzling的本質就是改變他們的對應關系,達到交換方法實現的目的。
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中的一個比較好的實戰使用方式,用好了的話可以解決各種各樣的問題,比如數組越界崩潰等等很多問題,都可以通過他來解決,但是也不能濫用,不小心使用的話,可能真的要半夜爬起來改代碼了。
本文章也僅限個人學習使用,如果有什么寫的不對的地方,還請各位大佬指正批評。