ios runtime淺析(三):Method Swizzling

看到nshipster 的Method Swizzling這篇不錯的文章還沒翻譯,就補充一下,沒有逐字翻譯,關于associated objects已經有翻譯了,大家也可以去了解一下。
??method swizzling也許是runtime中最有爭議的技術,它的作用就是改變已經存在selector的實現(xiàn),之所以可以這樣是因為方法調用可以在運行時改變:通過改變類的分發(fā)表( dispatch table,該表包含selector的名稱及對應實現(xiàn)函數(shù)的地址)里selector和實現(xiàn)之間的對應關系。
??舉個例子,比如你想記錄一個iOS應用里每個view controller顯示的次數(shù):可以在每個view controller添加記錄的代碼,但這會導致大量的重復代碼;通過繼承也是一個方法,但需要同時創(chuàng)建UIViewController, UITableViewController, UINavigationController及其它中view controller的子類,同樣也會產生許多重復的代碼出現(xiàn)。
??幸運的是,在UIViewController的category使用method swizzling:

#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);
        
        //交換實現(xiàn)
        method_exchangeImplementations(originalMethod, swizzledMethod);
    });
}

#pragma mark - Method Swizzling

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

@end

現(xiàn)在,當一個UIViewController或者其子類的實例調用viewWillAppear:方法時,就會打印出一條記錄。假如要在在view controller的生命周期,view的繪制或者Foundation的網絡協(xié)議棧注入一些自定的行為,method swizzling也許是你應該考慮的一個方向。

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

+load vs. +initialize

Swizzling應該只在load方法中使用

oc會在運行時自動調用每個類的兩個方法,+load 會在類初始化加載的時候調用;+initialize方法會在程序調用類的第一個實例或者類方法的時候調用。這兩個方法都是可選的,只會在實現(xiàn)的時候才去調用。由于method swizzling會影響到全局的狀態(tài),因此最小化競爭條件的出現(xiàn)變得很重要,+load方法能夠確保在類的初始化時候調用,這能夠保證改變應用行為的一致性,而+initialize在執(zhí)行時并不提供這種保證,實際上,如果沒有直接給這個類發(fā)送消息,該方法可能都不會調用到。

dispatch_once

Swizzling應該只在dispatch_once中完成

如上,由于swizzling會改變全局狀態(tài),所以我們需要在運行時采取一些預防措施。原子性就是其中的一種預防措施,因為它能保證不管有多少個線程,代碼只會執(zhí)行一次。GCD的dispatch_once 能夠滿足這種需求,因此在method swizzling應該將其作為最佳的實踐方式。

選擇器,方法和實現(xiàn)

在oc中,選擇器、方法和實現(xiàn)是運行時的特殊方面,雖然在一般情況下,這些術語是用在消息發(fā)送的過程中。
下面是Apple對它們的幾個描述:

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

調用 _cmd

看起來下面的代碼可能導致無限循環(huán):

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

可奇怪的是,它并不會。在swizzling的過程中,xxx_viewWillAppear:已經被重新指向UIViewController 的原始實現(xiàn)-viewWillAppear:,但是如果我們在這個方法中調用viewWillAppear:則會導致無限循環(huán)。

注意事項

通常認為Swizzling是一個比較危險的技術,容易產生不可預料的行為和無法預見的后果,但只要遵循以下幾個注意事項,其實method swizzlin還是相對安全的。

  • 總是調用一個方法的原始實現(xiàn)(除非你有足夠好的理由不這么做):API提供了輸入和輸出的約定,但其中的實現(xiàn)卻是黑盒。Swizzling 一個方法但不去調用其原始實現(xiàn),可能造成私有狀態(tài)的底層假設被打破,影響程序的其它部分。
  • 避免沖突: 給category方法加前綴,確保不會跟其它依賴的代碼產生沖突。
  • 知道到底發(fā)生啥了:簡單的復制粘貼swizzling 代碼,而不清楚其如何工作不僅非常危險,而且浪費了好多深入學習objective-c運行時的機會,可以通過查看 Objective-C Runtime Reference 和<objc/runtime.h>頭文件了解其中的一些來龍去脈。
  • 小心的處理:不管你在swizzling Foundation、UIKit或者其它內建framework方法時多么的充滿自信,必須清楚在下一個版本這些可能都改變了,所以為了不出差錯,還是需要多花點心思的。
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

  • 轉至元數(shù)據(jù)結尾創(chuàng)建: 董瀟偉,最新修改于: 十二月 23, 2016 轉至元數(shù)據(jù)起始第一章:isa和Class一....
    40c0490e5268閱讀 2,053評論 0 9
  • 目錄 Objective-C Runtime到底是什么 Objective-C的元素認知 Runtime詳解 應用...
    Ryan___閱讀 2,011評論 1 3
  • 這篇文章完全是基于南峰子老師博客的轉載 這篇文章完全是基于南峰子老師博客的轉載 這篇文章完全是基于南峰子老師博客的...
    西木閱讀 30,887評論 33 466
  • 本文轉載自:http://yulingtianxia.com/blog/2014/11/05/objective-...
    ant_flex閱讀 886評論 0 1
  • 繼上Runtime梳理(四) 通過前面的學習,我們了解到Objective-C的動態(tài)特性:Objective-C不...
    小名一峰閱讀 848評論 0 3

友情鏈接更多精彩內容