Objective-C黑魔法:Method Swizzling

Objective-C 中的 Method Swizzling 是一項(xiàng)異常強(qiáng)大的技術(shù)节吮,它可以允許我們動態(tài)地替換方法的實(shí)現(xiàn)脖隶,實(shí)現(xiàn)?Hook?功能,是一種比子類化更加靈活的“重寫”方法的方式。

Method Swizzling 的原理

Method Swizzling 是一把雙刃劍赎瑰,使用得當(dāng)可以讓我們非常輕松地實(shí)現(xiàn)復(fù)雜的功能薄声,而如果一旦誤用当船,它也很可能會給我們的程序帶來毀滅性的傷害。但是我們大可不必驚慌默辨,在了解了它的實(shí)現(xiàn)原理后德频,我們就可以“信手拈來”了。

我們先來了解下 Objective-C 中方法?Method?的數(shù)據(jù)結(jié)構(gòu):

本質(zhì)上廓奕,它就是?struct method_t?類型的指針抱婉,所以我們重點(diǎn)看下結(jié)構(gòu)體?method_t?的定義。在結(jié)構(gòu)體?method_t?中定義了三個(gè)成員變量和一個(gè)成員函數(shù):

1. name?表示的是方法的名稱桌粉,用于唯一標(biāo)識某個(gè)方法,比如?@selector(viewWillAppear:)?衙四;

2. types?表示的是方法的返回值和參數(shù)類型(詳細(xì)信息可以查閱蘋果官方文檔中的?Type Encodings)铃肯;

3. imp?是一個(gè)函數(shù)指針,指向方法的實(shí)現(xiàn)传蹈;

4. SortBySELAddress?顧名思義押逼,是一個(gè)根據(jù)?name?的地址對方法進(jìn)行排序的函數(shù)。

由此惦界,我們也可以發(fā)現(xiàn) Objective-C 中的方法名是不包括參數(shù)類型的挑格,也就是說下面兩個(gè)方法在 runtime 看來就是同一個(gè)方法:

而下面兩個(gè)方法卻是可以共存的:

因?yàn)閷?shí)例方法和類方法是分別保存在類對象和元類對象中的,更多詳情可以查看《Objective-C 對象模型》沾歪。

原則上漂彤,方法的名稱?name?和方法的實(shí)現(xiàn)?imp?是一一對應(yīng)的,而 Method Swizzling 的原理就是動態(tài)地改變它們的對應(yīng)關(guān)系灾搏,以達(dá)到替換方法實(shí)現(xiàn)的目的挫望。

Method Swizzling 有什么用

說了這么多,到底 Method Swizzling 有什么用呢狂窑?表猴急哈媳板,我們接下來看個(gè)例子就明白了。用過友盟統(tǒng)計(jì)的同學(xué)應(yīng)該知道泉哈,要實(shí)現(xiàn)頁面的統(tǒng)計(jì)功能蛉幸,我們需要在每個(gè)頁面的?view controller?中添加如下代碼:

要達(dá)到這個(gè)目的,我們有兩種比較常規(guī)的實(shí)現(xiàn)方式:

1. 直接修改每個(gè)頁面的?view controller?代碼丛晦,簡單粗暴奕纫;

2. 子類化?view controller?,并讓我們的?view controller?都繼承這些子類采呐。

第 1 種方式的缺點(diǎn)是不言而喻的若锁,這樣做不僅會產(chǎn)生大量重復(fù)的代碼,而且還很容易遺漏某些頁面斧吐,非常難維護(hù)又固;第 2 種方式稍微好一點(diǎn)仲器,但是也同樣需要我們子類化?UIViewController?、UITableViewController?和?UITabBarController?等不同類型的?view controller?仰冠。

也許你跟我一樣陷入了思考乏冀,難道就沒有一種簡單優(yōu)雅的解決方案嗎?答案是肯定的洋只,Method Swizzling 就是解決此類問題的最佳方式辆沦。

Method Swizzling 實(shí)踐

下面我們就以替換?viewWillAppear?方法為例談?wù)?Method Swizzling 的最佳實(shí)踐,話不多說识虚,直接上代碼:

解析:在上面的代碼中有三個(gè)關(guān)鍵點(diǎn)需要引起我們的注意:

1. 為什么是在?+load?方法中實(shí)現(xiàn) Method Swizzling 的邏輯肢扯,而不是其他的什么方法,比如?+initialize?等担锤;

2. 為什么 Method Swizzling 的邏輯需要用 dispatch_once 來進(jìn)行調(diào)度蔚晨;

3. 為什么需要調(diào)用?class_addMethod?方法,并且以它的結(jié)果為依據(jù)分別處理兩種不同的情況肛循。

下面我們就一起來分析下這三個(gè)為什么到底是為了什么铭腕?

第 1 個(gè)為什么:看過文章《Objective-C +load vs +initialize》?就會知道,+load?和?+initialize?是 Objective-C runtime 會自動調(diào)用的兩個(gè)類方法多糠。但是它們被調(diào)用的時(shí)機(jī)卻是有差別的累舷,+load?方法是在類被加載的時(shí)候調(diào)用的,而?+initialize?方法是在類或它的子類收到第一條消息之前被調(diào)用的夹孔,這里所指的消息包括實(shí)例方法和類方法的調(diào)用被盈。也就是說?+initialize?方法是以懶加載的方式被調(diào)用的,如果程序一直沒有給某個(gè)類或它的子類發(fā)送消息析蝴,那么這個(gè)類的?+initialize?方法是永遠(yuǎn)不會被調(diào)用的害捕。此外?+load?方法還有一個(gè)非常重要的特性,那就是子類闷畸、父類和分類中的?+load?方法的實(shí)現(xiàn)是被區(qū)別對待的尝盼。換句話說在 Objective-C runtime 自動調(diào)用?+load?方法時(shí),分類中的?+load?方法并不會對主類中的?+load?方法造成覆蓋佑菩。綜上所述盾沫,+load?方法是實(shí)現(xiàn) Method Swizzling 邏輯的最佳“場所”。

第 2 個(gè)為什么:我們上面提到殿漠,+load?方法在類加載的時(shí)候會被 runtime 自動調(diào)用一次赴精,但是它并沒有限制程序員對?+load?方法的手動調(diào)用。什么绞幌?你說不會有程序員這么干蕾哟?那可說不定,我還見過手動調(diào)用?viewDidLoad?方法的程序員,就是介么任性谭确。而我們所能夠做的就是盡可能地保證程序能夠在各種情況下正常運(yùn)行帘营。

第 3 個(gè)為什么:我們使用 Method Swizzling 的目的通常都是為了給程序增加功能,而不是完全地替換某個(gè)功能逐哈,所以我們一般都需要在自定義的實(shí)現(xiàn)中調(diào)用原始的實(shí)現(xiàn)芬迄。所以這里就會有兩種情況需要我們分別進(jìn)行處理:

第 1 種情況:主類本身有實(shí)現(xiàn)需要替換的方法,也就是?class_addMethod?方法返回?NO?昂秃。這種情況的處理比較簡單禀梳,直接交換兩個(gè)方法的實(shí)現(xiàn)就可以了:

第 2 種情況:主類本身沒有實(shí)現(xiàn)需要替換的方法,而是繼承了父類的實(shí)現(xiàn)肠骆,即?class_addMethod?方法返回?YES?算途。這時(shí)使用?class_getInstanceMethod?函數(shù)獲取到的?originalSelector?指向的就是父類的方法,我們再通過執(zhí)行?class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));將父類的實(shí)現(xiàn)替換到我們自定義的?mrc_viewWillAppear?方法中蚀腿。這樣就達(dá)到了在?mrc_viewWillAppear?方法的實(shí)現(xiàn)中調(diào)用父類實(shí)現(xiàn)的目的郊艘。

看到這里,相信你對 Method Swizzling 已經(jīng)有了一定的了解唯咬,那么接下來就請你自己親自試一試吧,you should give it a try yourself 畏浆。

總結(jié)

Method Swizzling 是一種黑魔法胆胰,我們在使用它時(shí)需要加倍小心。

參考鏈接

http://nshipster.com/method-swizzling/?

https://www.mikeash.com/pyblog/friday-qa-2010-01-29-method-replacement-for-fun-and-profit.html

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末刻获,一起剝皮案震驚了整個(gè)濱河市蜀涨,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌蝎毡,老刑警劉巖厚柳,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異沐兵,居然都是意外死亡别垮,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進(jìn)店門扎谎,熙熙樓的掌柜王于貴愁眉苦臉地迎上來碳想,“玉大人,你說我怎么就攤上這事毁靶‰时迹” “怎么了?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵预吆,是天一觀的道長龙填。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么岩遗? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任扇商,我火速辦了婚禮,結(jié)果婚禮上喘先,老公的妹妹穿的比我還像新娘钳吟。我一直安慰自己,他們只是感情好窘拯,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布红且。 她就那樣靜靜地躺著,像睡著了一般涤姊。 火紅的嫁衣襯著肌膚如雪暇番。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天思喊,我揣著相機(jī)與錄音壁酬,去河邊找鬼。 笑死恨课,一個(gè)胖子當(dāng)著我的面吹牛舆乔,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播剂公,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼希俩,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了纲辽?” 一聲冷哼從身側(cè)響起颜武,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎拖吼,沒想到半個(gè)月后鳞上,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡吊档,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年篙议,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片籍铁。...
    茶點(diǎn)故事閱讀 38,039評論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡涡上,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出拒名,到底是詐尸還是另有隱情吩愧,我是刑警寧澤,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布增显,位于F島的核電站雁佳,受9級特大地震影響脐帝,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜糖权,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一堵腹、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧星澳,春花似錦疚顷、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至如暖,卻和暖如春笆檀,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背盒至。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工酗洒, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人枷遂。 一個(gè)月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓樱衷,卻偏偏與公主長得像,于是被迫代替她去往敵國和親酒唉。 傳聞我的和親對象是個(gè)殘疾皇子箫老,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評論 2 345

推薦閱讀更多精彩內(nèi)容