Method Swizzling 的原理程序員
先打開 objc-private.h 文件 在 235行能夠看到 Method的定義:app
typedef struct method_t *Method;
而後在 objc-runtime-new.h 文件中第82行能夠看到:函數
struct method_t { SEL name; const char *types; IMP imp; struct SortBySELAddress : public std::binary_function<const method_t&, const method_t&, bool> { bool operator() (const method_t& lhs, const method_t& rhs) { return lhs.name < rhs.name; } }; };
從本質上講:它就是struct method_t 類型的執行,包括了3個成員變量和一個成員函數:
spa
name:表示的是方法的名稱,用於惟一標示該方法,好比@selector(viewWillAppear:);
指針
types:標示的是方法的返回值和參數類型;
code
imp:是一個函數指針,指向方法的實現;
對象
SortBySELAddress 是一個根據name的地址對方法進行排序的函數。排序
由上面能夠看出:Objective-C中的方法名是不包括參數類型的,也就是說下面2個方法在runtime看起來時同樣的。
繼承
- (void)viewWillAppear:(BOOL)animated; - (void)viewWillAppear:(NSString *)string;
並且,實例方法和類方法由於是分別保存在類對象和元類對象中的,全部,同名字的類方法和實例方法是能夠共存的。
get
原則上講:方法的名稱name和方法的實現imp是一一對應的,二Method Swizzling的原理就是動態改變他們的對應關係,以達到替換方法的目的。
講個簡單的例子,友盟統計的頁面統計的時候:
- (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; [MobClick beginLogPageView:@"PageOne"]; } - (void)viewWillDisappear:(BOOL)animated { [super viewWillDisappear:animated]; [MobClick endLogPageView:@"PageOne"]; }
要想偷懶解決上面的代碼,正常人的作法就是定義一個BaseViewController,而後其餘類都是從這個viewcontroller繼承出來的,可是,確實能夠很快搞定,問題是,若是是old項目要加入,並且原始項目還不是繼承某個BaseViewController的話就坑爹了,可是你還要一個一個類改繼承的viewcontroller,這個時候使用Method Swizzling是最佳的解決辦法。
Method Swizzling:
@interface UIViewController (MRCUMAnalytics) @end @implementation UIViewController (MRCUMAnalytics) + (void)load { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ Class class = [self class]; SEL originalSelector = @selector(viewWillAppear:); SEL swizzledSelector = @selector(mrc_viewWillAppear:); Method originalMethod = class_getInstanceMethod(class, originalSelector); Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector); BOOL success = class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod)); if (success) { class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod)); } else { method_exchangeImplementations(originalMethod, swizzledMethod); } }); } #pragma mark - Method Swizzling - (void)mrc_viewWillAppear:(BOOL)animated { [self mrc_viewWillAppear:animated]; [MobClick beginLogPageView:NSStringFromClass([self class])]; } @end
解析:在上面的代碼中有三個關鍵點須要引發咱們的注意:
爲何是在 +load 方法中實現 Method Swizzling 的邏輯,而不是其餘的什麼方法,好比 +initialize 等;
爲何 Method Swizzling 的邏輯須要用 dispatch_once 來進行調度;
爲何須要調用 class_addMethod 方法,而且以它的結果爲依據分別處理兩種不一樣的狀況。
第 1 個爲何:
+load 和 +initialize 是 Objective-C runtime 會自動調用的兩個類方法。可是它們被調用的時機倒是有差異的,+load 方法是在類被加載的時候調用的,而 +initialize 方法是在類或它的子類收到第一條消息以前被調用的,這裏所指的消息包括實例方法和類方法的調用。也就是說 +initialize 方法是以懶加載的方式被調用的,若是程序一直沒有給某個類或它的子類發送消息,那麼這個類的 +initialize 方法是永遠不會被調用的。此外 +load 方法還有一個很是重要的特性,那就是子類、父類和分類中的 +load 方法的實現是被區別對待的。換句話說在 Objective-C runtime 自動調用 +load 方法時,分類中的 +load 方法並不會對主類中的 +load 方法形成覆蓋。綜上所述,+load 方法是實現 Method Swizzling 邏輯的最佳「場所」。
第 2 個爲何:
咱們上面提到,+load 方法在類加載的時候會被 runtime 自動調用一次,可是它並無限制程序員對 +load 方法的手動調用。什麼?你說不會有程序員這麼幹?那可說不定,我還見過手動調用 viewDidLoad 方法的程序員,就是介麼任性。而咱們所可以作的就是儘量地保證程序可以在各類狀況下正常運行。
第 3 個爲何:
咱們使用 Method Swizzling 的目的一般都是爲了給程序增長功能,而不是徹底地替換某個功能,因此咱們通常都須要在自定義的實現中調用原始的實現。因此這裏就會有兩種狀況須要咱們分別進行處理:
第 1 種狀況:主類自己有實現須要替換的方法,也就是 class_addMethod 方法返回 NO 。這種狀況的處理比較簡單,直接交換兩個方法的實現就能夠了:
- (void)viewWillAppear:(BOOL)animated { /// 先調用原始實現,因爲主類自己有實現該方法,因此這裏實際調用的是主類的實現 [self mrc_viewWillAppear:animated]; /// 咱們增長的功能 [MobClick beginLogPageView:NSStringFromClass([self class])]; } - (void)mrc_viewWillAppear:(BOOL)animated { /// 主類的實現 }
第 2 種狀況:主類自己沒有實現須要替換的方法,而是繼承了父類的實現,即 class_addMethod 方法返回 YES 。這時使用 class_getInstanceMethod 函數獲取到的 originalSelector 指向的就是父類的方法,咱們再經過執行 class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod)); 將父類的實現替換到咱們自定義的 mrc_viewWillAppear 方法中。這樣就達到了在 mrc_viewWillAppear 方法的實現中調用父類實現的目的。
- (void)viewWillAppear:(BOOL)animated { /// 先調用原始實現,因爲主類自己並無實現該方法,因此這裏實際調用的是父類的實現 [self mrc_viewWillAppear:animated]; /// 咱們增長的功能 [MobClick beginLogPageView:NSStringFromClass([self class])]; } - (void)mrc_viewWillAppear:(BOOL)animated { /// 父類的實現 }
參考了 leichunfeng sunny yulingtianxia 等博客。