IOS 中方法重組(Method swizzling)

源自國內最大中文蘋果開發(fā)者網(wǎng)站:cocoachina
Method swizzling指的是改變一個已存在的選擇器對應的實現(xiàn)的過程掘剪,它依賴于Objectvie-C中方法的調用能夠在運行時進改變——通過改變類的調度表(dispatch table)中選擇器到最終函數(shù)間的映射關系樱调。

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

我們可以給每個視圖控制器對應的viewWillAppear:實現(xiàn)方法中增加相應的跟蹤代碼揪漩,但是這樣做會產生大量重復的代碼禾乘。子類化可能是另一個選擇橱健,但要求你將UIViewController斯撮、 UITableViewController、 UINavigationController 以及所有其他視圖控制器類都子類化础锐,這也會導致代碼重復嗓节。

幸好,還有另一個方法皆警,在分類中進行method swizzling拦宣,下面來看怎么做:
"#import

@implementation UIViewController (Tracking)

  • (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
    Class class = [self class];

      // 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 "
在計算機學科中,指針變換(pointer swizzling)是指將基于名字或位置的引用轉變?yōu)橹苯拥闹羔樢谩?然而在Objective-C中信姓,這個詞的起源并不完全知道鸵隧,但關于這一借鑒其實也很好理解,method swizzling可以通過選擇器來改變它引用的函數(shù)指針意推。

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

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

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

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

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

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

還是因為swizzling會改變全局费坊,我們需要在運行時采取所有可用的防范措施倒槐。保障原子性就是一個措施,它確保代碼即使在多線程環(huán)境下也只會被執(zhí)行一次附井。GCD中的diapatch_once就提供這些保障讨越,它應該被當做swizzling的標準實踐两残。

選擇器、方法及實現(xiàn)
在Objective-C中把跨,盡管這些詞經常被放在一起來描述消息傳遞的過程人弓,但選擇器、方法及實現(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ù)使用的是基于當前CPU架構的標準C調用規(guī)約太雨。第一個參數(shù)是指向self的指針(也就是該類的某個 實例的內存空間吟榴,或者對于類方法來說,是指向元類(metaclass)的指針)囊扳。第二個參數(shù)是方法的選擇器吩翻,后面跟的都是參數(shù)。

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

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

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

  • (void)xxx_viewWillAppear:(BOOL)animated {
    [self xxx_viewWillAppear:animated];
    NSLog(@"viewWillAppear: %@", NSStringFromClass([self class]));
    }

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

記住給swizzled方法加上前綴引谜,這和你需要給可能產生沖突的分類方法加前綴是一個道理牍陌。

注意事項
Swizzling被普遍認為是一種巫術,容易導致不可預料的行為和結果员咽。盡管不是最安全的,但是如果你采取下面這些措施贮预,method swizzling還是很安全的贝室。

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

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

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

謹慎行事:不管你多么自信你能夠swizzling Foundation夯辖、UIKit 或者其他內置框架琉预,請記住所有這些都可能在下一個版本中就不好使。提前做好準備蒿褂,防范于未然才不至于到時候焦頭爛額圆米。

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

與associated objects一樣昙楚,method swizzling是一個強大的技術近速,但是你也應該謹慎使用。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末桂肌,一起剝皮案震驚了整個濱河市数焊,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌崎场,老刑警劉巖佩耳,帶你破解...
    沈念sama閱讀 216,997評論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異谭跨,居然都是意外死亡干厚,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,603評論 3 392
  • 文/潘曉璐 我一進店門螃宙,熙熙樓的掌柜王于貴愁眉苦臉地迎上來蛮瞄,“玉大人,你說我怎么就攤上這事谆扎」彝保” “怎么了?”我有些...
    開封第一講書人閱讀 163,359評論 0 353
  • 文/不壞的土叔 我叫張陵堂湖,是天一觀的道長闲先。 經常有香客問我状土,道長,這世上最難降的妖魔是什么伺糠? 我笑而不...
    開封第一講書人閱讀 58,309評論 1 292
  • 正文 為了忘掉前任蒙谓,我火速辦了婚禮,結果婚禮上训桶,老公的妹妹穿的比我還像新娘累驮。我一直安慰自己,他們只是感情好舵揭,可當我...
    茶點故事閱讀 67,346評論 6 390
  • 文/花漫 我一把揭開白布谤专。 她就那樣靜靜地躺著,像睡著了一般琉朽。 火紅的嫁衣襯著肌膚如雪毒租。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,258評論 1 300
  • 那天箱叁,我揣著相機與錄音墅垮,去河邊找鬼。 笑死耕漱,一個胖子當著我的面吹牛算色,可吹牛的內容都是我干的。 我是一名探鬼主播螟够,決...
    沈念sama閱讀 40,122評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼灾梦,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了妓笙?” 一聲冷哼從身側響起若河,我...
    開封第一講書人閱讀 38,970評論 0 275
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎寞宫,沒想到半個月后萧福,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經...
    沈念sama閱讀 45,403評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡辈赋,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,596評論 3 334
  • 正文 我和宋清朗相戀三年鲫忍,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片钥屈。...
    茶點故事閱讀 39,769評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡悟民,死狀恐怖,靈堂內的尸體忽然破棺而出篷就,到底是詐尸還是另有隱情射亏,我是刑警寧澤,帶...
    沈念sama閱讀 35,464評論 5 344
  • 正文 年R本政府宣布,位于F島的核電站鸦泳,受9級特大地震影響银锻,放射性物質發(fā)生泄漏永品。R本人自食惡果不足惜做鹰,卻給世界環(huán)境...
    茶點故事閱讀 41,075評論 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望鼎姐。 院中可真熱鬧钾麸,春花似錦、人聲如沸炕桨。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,705評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽献宫。三九已至钥平,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間姊途,已是汗流浹背涉瘾。 一陣腳步聲響...
    開封第一講書人閱讀 32,848評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留捷兰,地道東北人立叛。 一個月前我還...
    沈念sama閱讀 47,831評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像贡茅,于是被迫代替她去往敵國和親秘蛇。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,678評論 2 354

推薦閱讀更多精彩內容

  • 轉至元數(shù)據(jù)結尾創(chuàng)建: 董瀟偉顶考,最新修改于: 十二月 23, 2016 轉至元數(shù)據(jù)起始第一章:isa和Class一....
    40c0490e5268閱讀 1,709評論 0 9
  • 這篇文章完全是基于南峰子老師博客的轉載 這篇文章完全是基于南峰子老師博客的轉載 這篇文章完全是基于南峰子老師博客的...
    西木閱讀 30,554評論 33 466
  • 我們常常會聽說 Objective-C 是一門動態(tài)語言赁还,那么這個「動態(tài)」表現(xiàn)在哪呢?我想最主要的表現(xiàn)就是 Obje...
    Ethan_Struggle閱讀 2,192評論 0 7
  • 目錄 Objective-C Runtime到底是什么 Objective-C的元素認知 Runtime詳解 應用...
    Ryan___閱讀 1,939評論 1 3
  • 本文轉載自:http://yulingtianxia.com/blog/2014/11/05/objective-...
    ant_flex閱讀 758評論 0 1