iOS開發-runtime-黑魔法Method swizzling

記得很久很久以前,啊,其實也沒有很久,有一次面試,面試官問我,你懂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方法中,有兩個關鍵的成員變量: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中的一個比較好的實戰使用方式,用好了的話可以解決各種各樣的問題,比如數組越界崩潰等等很多問題,都可以通過他來解決,但是也不能濫用,不小心使用的話,可能真的要半夜爬起來改代碼了。
本文章也僅限個人學習使用,如果有什么寫的不對的地方,還請各位大佬指正批評。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,001評論 6 537
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,786評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,986評論 0 381
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,204評論 1 315
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,964評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,354評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,410評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,554評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,106評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,918評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,093評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,648評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,342評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,755評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,009評論 1 289
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,839評論 3 395
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,107評論 2 375

推薦閱讀更多精彩內容