Method Swizzling

Method Swizzling

發(fā)布于:2014-02-25 10:44閱讀數(shù): http://www.cocoachina.com/industry/20140225/7880.html

Method swizzling指的是改變一個已存在的選擇器對應(yīng)的實現(xiàn)的過程,它依賴于Objectvie-C中方法的調(diào)用能夠在運行時進(jìn)改變——通過改變類的調(diào)度表(dispatch table)中選擇器到最終函數(shù)間的映射關(guān)系。

“”

閱讀器

iOS開發(fā)iOS

本文由TracyYih[博客]翻譯自NSHipster的文章Method Swizzling。

在上周associated objects一文中,我們開始探索Objective-C運行時的一些黑魔法。本周我們繼續(xù)前行,來討論可能是最受爭議的運行時技術(shù):method swizzling。

Method swizzling指的是改變一個已存在的選擇器對應(yīng)的實現(xiàn)的過程,它依賴于Objectvie-C中方法的調(diào)用能夠在運行時進(jìn)改變——通過改變類的調(diào)度表(dispatch table)中選擇器到最終函數(shù)間的映射關(guān)系。

舉個例子,假設(shè)我們想跟蹤在一個iOS應(yīng)用中每個視圖控制器展現(xiàn)給用戶的次數(shù):

我們可以給每個視圖控制器對應(yīng)的viewWillAppear:實現(xiàn)方法中增加相應(yīng)的跟蹤代碼,但是這樣做會產(chǎn)生大量重復(fù)的代碼。子類化可能是另一個選擇,但要求你將UIViewController、UITableViewController、UINavigationController以及所有其他視圖控制器類都子類化,這也會導(dǎo)致代碼重復(fù)。

幸好,還有另一個方法,在分類中進(jìn)行method swizzling,下面來看怎么做:

#import?

@implementation?UIViewController?(Tracking)

+?(void)load?{

staticdispatch_once_t?onceToken;

dispatch_once(&onceToken,?^{

Classclass=?[selfclass];

//?When?swizzling?a?class?method,?use?the?following:

//?Class?class?=?object_getClass((id)self);

SEL?originalSelector?=?@selector(viewWillAppear:);

SEL?swizzledSelector?=?@selector(xxx_viewWillAppear:);

Method?originalMethod?=?class_getInstanceMethod(class,?originalSelector);

Method?swizzledMethod?=?class_getInstanceMethod(class,?swizzledSelector);

BOOL?didAddMethod?=

class_addMethod(class,

originalSelector,

method_getImplementation(swizzledMethod),

method_getTypeEncoding(swizzledMethod));

if(didAddMethod)?{

class_replaceMethod(class,

swizzledSelector,

method_getImplementation(originalMethod),

method_getTypeEncoding(originalMethod));

}else{

method_exchangeImplementations(originalMethod,?swizzledMethod);

}

});

}

#pragma?mark?-?Method?Swizzling

-?(void)xxx_viewWillAppear:(BOOL)animated?{

[self?xxx_viewWillAppear:animated];

NSLog(@"viewWillAppear:?%@",?self);

}

@end

在計算機學(xué)科中,指針變換(pointer swizzling)是指將基于名字或位置的引用轉(zhuǎn)變?yōu)橹苯拥闹羔樢谩?然而在Objective-C中,這個詞的起源并不完全知道,但關(guān)于這一借鑒其實也很好理解,method swizzling可以通過選擇器來改變它引用的函數(shù)指針。

現(xiàn)在,當(dāng)UIViewController或它子類的任何實例觸發(fā)viewWillAppear:方法都會打印一條log日志。

向視圖控制器的生命周期中注入操作、事件的響應(yīng)、視圖的繪制,或Foundation中的網(wǎng)絡(luò)堆棧都是能夠利用method swizzling產(chǎn)生明顯效果的場景。還有一些其他的場景使用swizzling會是一個合適的選擇,這隨著Objective-C開發(fā)者經(jīng)驗不斷豐富會變得越來越明顯。

先不說為什么和在哪些地方使用swizzling,來看一下應(yīng)該怎樣實現(xiàn):

+load vs. +initialize

Swizzling應(yīng)該在+load方法中實現(xiàn)。

每個類的這兩個方法會被Objective-C運行時系統(tǒng)自動調(diào)用,+load是在一個類最開始加載時調(diào)用,+initialize是在應(yīng)用中第一次調(diào)用該類或它的實例的方式之前調(diào)用。這兩個方法都是可選的,只有實現(xiàn)了才會被執(zhí)行。

因為method swizzling會影響全局,所以減少冒險情況就很重要。+load能夠保證在類初始化的時候就會被加載,這為改變系統(tǒng)行為提供了一些統(tǒng)一性。但+initialize并不能保證在什么時候被調(diào)用——事實上也有可能永遠(yuǎn)也不會被調(diào)用,例如應(yīng)用程序從未直接的給該類發(fā)送消息。

dispatch_once

Swizzling應(yīng)該在dispatch_once中實現(xiàn)。

還是因為swizzling會改變?nèi)?,我們需要在運行時采取所有可用的防范措施。保障原子性就是一個措施,它確保代碼即使在多線程環(huán)境下也只會被執(zhí)行一次。GCD中的diapatch_once就提供這些保障,它應(yīng)該被當(dāng)做swizzling的標(biāo)準(zhǔn)實踐。

選擇器、方法及實現(xiàn)

在Objective-C中,盡管這些詞經(jīng)常被放在一起來描述消息傳遞的過程,但選擇器、方法及實現(xiàn)分別代表運行時的不同方面。

下面是蘋果Objective-C Runtime Reference文檔中對它們的描述:

1.選擇器(typedef struct objc_selector *SEL):選擇器用于表示一個方法在運行時的名字,一個方法的選擇器是一個注冊到(或映射到)Objective-C運行時中的C字符串,它是由編譯器生成并在類加載的時候被運行時系統(tǒng)自動映射。

2.方法(typedef struct objc_method *Method):一個代表類定義中一個方法的不明類型。

3.實現(xiàn)(typedef id (*IMP)(id, SEL, ...)):這種數(shù)據(jù)類型是實現(xiàn)某個方法的函數(shù)開始位置的指針,函數(shù)使用的是基于當(dāng)前CPU架構(gòu)的標(biāo)準(zhǔn)C調(diào)用規(guī)約。第一個參數(shù)是指向self的指針(也就是該類的某個實例的內(nèi)存空間,或者對于類方法來說,是指向元類(metaclass)的指針)。第二個參數(shù)是方法的選擇器,后面跟的都是參數(shù)。

理解這些概念之間關(guān)系最好的方式是:一個類(Class)維護(hù)一張調(diào)度表(dispatch table)用于解析運行時發(fā)送的消息;調(diào)度表中的每個實體(entry)都是一個方法(Method),其中key值是一個唯一的名字——選擇器(SEL),它對應(yīng)到一個實現(xiàn)(IMP)——實際上就是指向標(biāo)準(zhǔn)C函數(shù)的指針。

Method Swizzling就是改變類的調(diào)度表讓消息解析時從一個選擇器對應(yīng)到另外一個的實現(xiàn),同時將原始的方法實現(xiàn)混淆到一個新的選擇器。

調(diào)用_cmd

下面這段代碼看起來像是會導(dǎo)致一個死循環(huán):

-?(void)xxx_viewWillAppear:(BOOL)animated?{

[self?xxx_viewWillAppear:animated];

NSLog(@"viewWillAppear:?%@",?NSStringFromClass([selfclass]));

}

但其實并沒有,在Swizzling的過程中,xxx_viewWillAppear:會被重新分配給UIViewController的-viewWillAppear:的原始實現(xiàn)。一個優(yōu)秀程序員應(yīng)有的直覺會告訴你在一個方法的實現(xiàn)中通過self調(diào)用當(dāng)前方法自身會產(chǎn)生錯誤,但是在當(dāng)前這種情況下,如果我們記住到底是怎么回事更有意義。反而,如果我們在這個方法中調(diào)用viewWillAppear:才會真的導(dǎo)致死循環(huán),因為這個方法的實現(xiàn)會在運行時被swizzle到viewWillAppear:的選擇器。

記住給swizzled方法加上前綴,這和你需要給可能產(chǎn)生沖突的分類方法加前綴是一個道理。

注意事項

Swizzling被普遍認(rèn)為是一種巫術(shù),容易導(dǎo)致不可預(yù)料的行為和結(jié)果。盡管不是最安全的,但是如果你采取下面這些措施,method swizzling還是很安全的。

1.始終調(diào)用方法的原始實現(xiàn)(除非你有足夠的理由不這么做):API為輸入和輸出提供規(guī)約,但它里面具體的實現(xiàn)其實是個黑匣子,在Method Swizzling過程中不調(diào)用它原始的實現(xiàn)可能會破壞一些私有狀態(tài),甚至是程序的其他部分。

2.避免沖突:給分類方法加前綴,一定要確保不要讓你代碼庫中其他代碼(或是依賴庫)在做與你相同的事。

3.理解:只是簡單的復(fù)制粘貼swizzling代碼而不去理解它是怎么運行的,這不僅非常危險,而且還浪費了學(xué)習(xí)Objective-C運行時的機會。閱讀Objective-C Runtime Reference和去理解代碼是怎樣和為什么這樣執(zhí)行的,努力的用你的理解來消滅你的疑惑。

謹(jǐn)慎行事:不管你多么自信你能夠swizzling Foundation、UIKit 或者其他內(nèi)置框架,請記住所有這些都可能在下一個版本中就不好使。提前做好準(zhǔn)備,防范于未然才不至于到時候焦頭爛額。

不敢放心大膽的直接使用Objective-C運行時?Jonathan ‘Wolf’ Rentzsch提供了經(jīng)過實戰(zhàn)檢驗的、支持CocoaPads的庫JRSwizzle,它會為你考慮好了一切。

與associated objects一樣,method swizzling是一個強大的技術(shù),但是你也應(yīng)該謹(jǐn)慎使用。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 轉(zhuǎn)至元數(shù)據(jù)結(jié)尾創(chuàng)建: 董瀟偉,最新修改于: 十二月 23, 2016 轉(zhuǎn)至元數(shù)據(jù)起始第一章:isa和Class一....
    40c0490e5268閱讀 2,068評論 0 9
  • 源自國內(nèi)最大中文蘋果開發(fā)者網(wǎng)站:cocoachinaMethod swizzling指的是改變一個已存在的選擇器對...
    UILabelkell閱讀 469評論 0 1
  • 本文轉(zhuǎn)載自:http://southpeak.github.io/2014/11/06/objective-c-r...
    idiot_lin閱讀 179評論 0 0
  • Method swizzling指的是改變一個已存在的選擇器對應(yīng)的實現(xiàn)的過程,它依賴于Objectvie-C中方法...
    Visitor閱讀 164評論 0 0
  • 日記本一頁頁翻過,有許多撕去的殘根,已經(jīng)想不起那些碎片記錄了什么,總之自己看了都尷尬,就撕去了,當(dāng)什么都沒發(fā)生過。...
    Believe謙宇千尋閱讀 248評論 0 0

友情鏈接更多精彩內(nèi)容