原文出處:南峰子的技術(shù)博客
理解Method Swizzling是學(xué)習(xí)runtime機(jī)制的一個(gè)很好的機(jī)會(huì)待榔。在此不多做整理乾闰,僅翻譯由Mattt Thompson發(fā)表于nshipster的Method Swizzling一文奢方。
Method Swizzling是改變一個(gè)selector的實(shí)際實(shí)現(xiàn)的技術(shù)琳拨。通過這一技術(shù)戈盈,我們可以在運(yùn)行時(shí)通過修改類的分發(fā)表中selector對(duì)應(yīng)的函數(shù)氓侧,來修改方法的實(shí)現(xiàn)。
例如济赎,我們想跟蹤程序中每一個(gè)view controller展示給用戶的次數(shù):當(dāng)然,我們可以在每個(gè)view controller的viewDidAppear中添加跟蹤代碼记某;但是這太過麻煩司训,需要在每個(gè)view controller中寫重復(fù)的代碼。創(chuàng)建一個(gè)子類可能是一種實(shí)現(xiàn)方式液南,但需要同時(shí)創(chuàng)建UIViewController, UITableViewController, UINavigationController及其它UIKit中view controller的子類壳猜,這同樣會(huì)產(chǎn)生許多重復(fù)的代碼。
這種情況下滑凉,我們就可以使用Method Swizzling统扳,如在代碼所示:
在這里,我們通過method swizzling修改了UIViewController的@selector(viewWillAppear:)對(duì)應(yīng)的函數(shù)指針畅姊,使其實(shí)現(xiàn)指向了我們自定義的swizz_viewWillAppear的實(shí)現(xiàn)咒钟。這樣,當(dāng)UIViewController及其子類的對(duì)象調(diào)用viewWillAppear時(shí)若未,都會(huì)打印一條日志信息朱嘴。(下面是貼上我的打印結(jié)果)
上面的例子很好地展示了使用method swizzling向一個(gè)類中注入一些我們新的操作。當(dāng)然粗合,還有許多場(chǎng)景可以使用method swizzling萍嬉,在此不多舉例。在此我們說說使用method swizzling需要注意的一些問題:
Swizzling應(yīng)該總是在+load中執(zhí)行
在Objective-C中隙疚,運(yùn)行時(shí)會(huì)自動(dòng)調(diào)用每個(gè)類的兩個(gè)方法壤追。+load會(huì)在類初始加載時(shí)調(diào)用,+initialize會(huì)在第一次調(diào)用類的類方法或?qū)嵗椒ㄖ氨徽{(diào)用供屉。這兩個(gè)方法是可選的行冰,且只有在實(shí)現(xiàn)了它們時(shí)才會(huì)被調(diào)用溺蕉。由于method swizzling會(huì)影響到類的全局狀態(tài),因此要盡量避免在并發(fā)處理中出現(xiàn)競(jìng)爭(zhēng)的情況资柔。+load能保證在類的初始化過程中被加載焙贷,并保證這種改變應(yīng)用級(jí)別的行為的一致性。相比之下贿堰,+initialize在其執(zhí)行時(shí)不提供這種保證—事實(shí)上辙芍,如果在應(yīng)用中沒為給這個(gè)類發(fā)送消息,則它可能永遠(yuǎn)不會(huì)被調(diào)用羹与。
Swizzling應(yīng)該總是在dispatch_once中執(zhí)行
與上面相同故硅,因?yàn)閟wizzling會(huì)改變?nèi)譅顟B(tài),所以我們需要在運(yùn)行時(shí)采取一些預(yù)防措施纵搁。原子性就是這樣一種措施吃衅,它確保代碼只被執(zhí)行一次,不管有多少個(gè)線程腾誉。GCD的dispatch_once可以確保這種行為徘层,我們應(yīng)該將其作為method swizzling的最佳實(shí)踐。
選擇器利职、方法與實(shí)現(xiàn)
在Objective-C中趣效,選擇器(selector)、方法(method)和實(shí)現(xiàn)(implementation)是運(yùn)行時(shí)中一個(gè)特殊點(diǎn)猪贪,雖然在一般情況下跷敬,這些術(shù)語(yǔ)更多的是用在消息發(fā)送的過程描述中。
以下是Objective-C Runtime Reference中的對(duì)這幾個(gè)術(shù)語(yǔ)一些描述:
1)Selector(typedef struct objc_selector *SEL):用于在運(yùn)行時(shí)中表示一個(gè)方法的名稱热押。一個(gè)方法選擇器是一個(gè)C字符串西傀,它是在Objective-C運(yùn)行時(shí)被注冊(cè)的。選擇器由編譯器生成桶癣,并且在類被加載時(shí)由運(yùn)行時(shí)自動(dòng)做映射操作拥褂。
2)Method(typedef struct objc_method *Method):在類定義中表示方法的類型
3)Implementation(typedef id (*IMP)(id, SEL, …)):這是一個(gè)指針類型,指向方法實(shí)現(xiàn)函數(shù)的開始位置鬼廓。這個(gè)函數(shù)使用為當(dāng)前CPU架構(gòu)實(shí)現(xiàn)的標(biāo)準(zhǔn)C調(diào)用規(guī)范肿仑。每一個(gè)參數(shù)是指向?qū)ο笞陨淼闹羔?self),第二個(gè)參數(shù)是方法選擇器碎税。然后是方法的實(shí)際參數(shù)尤慰。
理解這幾個(gè)術(shù)語(yǔ)之間的關(guān)系最好的方式是:一個(gè)類維護(hù)一個(gè)運(yùn)行時(shí)可接收的消息分發(fā)表;分發(fā)表中的每個(gè)入口是一個(gè)方法(Method)雷蹂,其中key是一個(gè)特定名稱伟端,即選擇器(SEL),其對(duì)應(yīng)一個(gè)實(shí)現(xiàn)(IMP)匪煌,即指向底層C函數(shù)的指針责蝠。
為了swizzle一個(gè)方法党巾,我們可以在分發(fā)表中將一個(gè)方法的現(xiàn)有的選擇器映射到不同的實(shí)現(xiàn),而將該選擇器對(duì)應(yīng)的原始實(shí)現(xiàn)關(guān)聯(lián)到一個(gè)新的選擇器中霜医。
調(diào)用_cmd
我們回過頭來看看前面新的方法的實(shí)現(xiàn)代碼:
咋看上去是會(huì)導(dǎo)致無(wú)限循環(huán)的齿拂。但令人驚奇的是,并沒有出現(xiàn)這種情況肴敛。在swizzling的過程中署海,方法中的[self swizz_viewWillAppear:animated]已經(jīng)被重新指定到UIViewController類的-viewWillAppear:中。在這種情況下医男,不會(huì)產(chǎn)生無(wú)限循環(huán)砸狞。不過如果我們調(diào)用的是[self viewWillAppear:animated],則會(huì)產(chǎn)生無(wú)限循環(huán)镀梭,因?yàn)檫@個(gè)方法的實(shí)現(xiàn)在運(yùn)行時(shí)已經(jīng)被重新指定為swizz_viewWillAppear:了刀森。
注意事項(xiàng)
Swizzling通常被稱作是一種黑魔法,容易產(chǎn)生不可預(yù)知的行為和無(wú)法預(yù)見的后果报账。雖然它不是最安全的研底,但如果遵從以下幾點(diǎn)預(yù)防措施的話,還是比較安全的:
1)總是調(diào)用方法的原始實(shí)現(xiàn)(除非有更好的理由不這么做):API提供了一個(gè)輸入與輸出約定透罢,但其內(nèi)部實(shí)現(xiàn)是一個(gè)黑盒飘哨。Swizzle一個(gè)方法而不調(diào)用原始實(shí)現(xiàn)可能會(huì)打破私有狀態(tài)底層操作,從而影響到程序的其它部分琐凭。
2)避免沖突:給自定義的分類方法加前綴,從而使其與所依賴的代碼庫(kù)不會(huì)存在命名沖突浊服。
3)明白是怎么回事:簡(jiǎn)單地拷貝粘貼swizzle代碼而不理解它是如何工作的统屈,不僅危險(xiǎn),而且會(huì)浪費(fèi)學(xué)習(xí)Objective-C運(yùn)行時(shí)的機(jī)會(huì)牙躺。閱讀Objective-C Runtime Reference和查看頭文件以了解事件是如何發(fā)生的愁憔。
4)小心操作:無(wú)論我們對(duì)Foundation, UIKit或其它內(nèi)建框架執(zhí)行Swizzle操作抱有多大信心,需要知道在下一版本中許多事可能會(huì)不一樣孽拷。