IOS開發-ios runtime-Method Swizzling

method swizzling也許是runtime中最有爭議的技術,它的做用就是改變已經存在selector的實現,之因此能夠這樣是由於方法調用能夠在運行時改變:經過改變類的分發表( dispatch table,該表包含selector的名稱及對應實現函數的地址)裏selector和實現之間的對應關係。
  舉個例子,好比你想記錄一個iOS應用裏每一個view controller顯示的次數:能夠在每一個view controller添加記錄的代碼,但這會致使大量的重複代碼;經過繼承也是一個方法,但須要同時建立UIViewController, UITableViewController, UINavigationController及其它中view controller的子類,一樣也會產生許多重複的代碼出現。
  幸運的是,在UIViewController的category使用method swizzling:html

#import <objc/runtime.h> @implementation UIViewController (Tracking) + (void)load { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ Class class = [self class]; SEL originalSelector = @selector(viewWillAppear:); SEL swizzledSelector = @selector(xxx_viewWillAppear:); Method originalMethod = class_getInstanceMethod(class, originalSelector); Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector); // 若是 swizzling 的是類方法, 採用以下的方式: // Class class = object_getClass((id)self); // ... // Method originalMethod = class_getClassMethod(class, originalSelector); // Method swizzledMethod = class_getClassMethod(class, swizzledSelector); //交換實現 method_exchangeImplementations(originalMethod, swizzledMethod); }); } #pragma mark - Method Swizzling - (void)xxx_viewWillAppear:(BOOL)animated { [self xxx_viewWillAppear:animated]; NSLog(@"viewWillAppear: %@", self); } @end

  如今,當一個UIViewController或者其子類的實例調用viewWillAppear:方法時,就會打印出一條記錄。假如要在在view controller的生命週期,view的繪製或者Foundation的網絡協議棧注入一些自定的行爲,method swizzling也許是你應該考慮的一個方向。objective-c

下面是使用method swizzling應該注意的點:安全

+load vs. +initialize

Swizzling應該只在load方法中使用

oc會在運行時自動調用每一個類的兩個方法,+load 會在類初始化加載的時候調用;+initialize方法會在程序調用類的第一個實例或者類方法的時候調用。這兩個方法都是可選的,只會在實現的時候纔去調用。因爲method swizzling會影響到全局的狀態,所以最小化競爭條件的出現變得很重要,+load方法可以確保在類的初始化時候調用,這可以保證改變應用行爲的一致性,而+initialize在執行時並不提供這種保證,實際上,若是沒有直接給這個類發送消息,該方法可能都不會調用到。網絡

dispatch_once

Swizzling應該只在dispatch_once中完成

如上,因爲swizzling會改變全局狀態,因此咱們須要在運行時採起一些預防措施。原子性就是其中的一種預防措施,由於它能保證無論有多少個線程,代碼只會執行一次。GCD的dispatch_once 可以知足這種需求,所以在method swizzling應該將其做爲最佳的實踐方式。app

選擇器,方法和實現

在oc中,選擇器、方法和實現是運行時的特殊方面,雖然在通常狀況下,這些術語是用在消息發送的過程當中。
下面是Apple對它們的幾個描述:函數

  • 選擇器(Selector-typedef struct objc_selector *SEL ):用於在運行時表示一個方法的名稱,一個方法選擇器就是一個C字符串,在運行時會被註冊或者映射,選擇器是由編譯器生成的,並在類被加載的時候由運行時自動進行映射。
  • 方法(Method-typedef struct objc_method *Method):在類的定義中表明一個方法的類型。
  • 實現(Implementation- typedef id (*IMP)(id, SEL, ...)):這是一個指向方法實現函數起始地址的指針,這個函數的第一個參數是指向self的指針,第二個參數是方法選擇器,而後是方法的參數。
    理解它們之間關係的最好方式是:一個類維護着一張分發表(dispatch table),用來解析運行時發來的消息;該表的每一個入口是一個方法(Method),其中的key就是選擇器(SEL),對應一個實現(IMP),即一個指向底層c函數的指針。
    swizzle 一個方法就是改變類的分發表,使它在解析消息的時候,將一個選擇器selector對應到別的實現,而且將該選擇器對應的原始實現關聯到新的選擇器中。

    調用 _cmd

    看起來下面的代碼可能致使無限循環:
- (void)xxx_viewWillAppear:(BOOL)animated { [self xxx_viewWillAppear:animated]; NSLog(@"viewWillAppear: %@", NSStringFromClass([self class])); }

可奇怪的是,它並不會。在swizzling的過程當中,xxx_viewWillAppear:已經被從新指向UIViewController 的原始實現-viewWillAppear:,可是若是咱們在這個方法中調用viewWillAppear:則會致使無限循環。學習

注意事項

一般認爲Swizzling是一個比較危險的技術,容易產生不可預料的行爲和沒法預見的後果,但只要遵循如下幾個注意事項,其實method swizzlin仍是相對安全的。ui

  • 老是調用一個方法的原始實現(除非你有足夠好的理由不這麼作):API提供了輸入和輸出的約定,但其中的實現倒是黑盒。Swizzling 一個方法但不去調用其原始實現,可能形成私有狀態的底層假設被打破,影響程序的其它部分。
  • 避免衝突: 給category方法加前綴,確保不會跟其它依賴的代碼產生衝突。
  • 知道到底發生啥了:簡單的複製粘貼swizzling 代碼,而不清楚其如何工做不只很是危險,並且浪費了好多深刻學習objective-c運行時的機會,能夠經過查看 Objective-C Runtime Reference 和<objc/runtime.h>頭文件瞭解其中的一些前因後果。
  • 當心的處理:無論你在swizzling Foundation、UIKit或者其它內建framework方法時多麼的充滿自信,必須清楚在下一個版本這些可能都改變了,因此爲了避免出差錯,仍是須要多花點心思的。
相關文章
相關標籤/搜索