能夠將Method Swizzling功能封裝爲類方法,做爲NSObject的類別安全
#import <Foundation/Foundation.h> #import <objc/runtime.h> @interface NSObject (Swizzling) + (void)methodSwizzlingWithOriginalSelector:(SEL)originalSelector bySwizzledSelector:(SEL)swizzledSelector; @end import "NSObject+Swizzling.h" @implementation NSObject (Swizzling) + (void)methodSwizzlingWithOriginalSelector:(SEL)originalSelector bySwizzledSelector:(SEL)swizzledSelector{ Class class = [self class]; //原有方法 Method originalMethod = class_getInstanceMethod(class, originalSelector); //替換原有方法的新方法 Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector); //先嚐試給源SEL添加IMP,這裏是爲了不源SEL沒有實現IMP的狀況 BOOL didAddMethod = class_addMethod(class,originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod)); if (didAddMethod) { //添加成功:說明源SEL沒有實現IMP,將源SEL的IMP替換到交換SEL的IMP class_replaceMethod(class,swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod)); } else { //添加失敗:說明源SEL已經有IMP,直接將兩個SEL的IMP交換便可 method_exchangeImplementations(originalMethod, swizzledMethod); } } @end
在運行時,類(Class)維護了一個消息分發列表來解決消息的正確發送。每個消息列表的入口是一個方法(Method),這個方法映射了一對鍵值對,其中鍵是這個方法的名字(SEL),值是指向這個方法實現的函數指針 implementation(IMP)多線程
僞代碼表示: Class { MethodList ( Method{ SEL:IMP; } Method{ SEL:IMP; } ); };
Method Swizzling就是改變類的消息分發列表來讓消息解析時從一個選擇器(SEL)對應到另一個的實現(IMP),同時將原始的方法實現混淆到一個新的選擇器(SEL)。ide
3.爲何要添加didAddMethod判斷函數
先嚐試添加原SEL實際上是爲了作一層保護,由於若是這個類沒有實現originalSelector,但其父類實現了,那class_getInstanceMethod會返回父類的方法。這樣method_exchangeImplementations替換的是父類的那個方法,這固然不是咱們想要的。因此咱們先嚐試添加 orginalSelector,若是已經存在,再用 method_exchangeImplementations 把原方法的實現跟新的方法實現給交換掉。
若是理解還不夠透徹,咱們能夠進入runtime.h中查看class_addMethod源碼解釋this
/** * Adds a new method to a class with a given name and implementation. * * @param cls The class to which to add a method. * @param name A selector that specifies the name of the method being added. * @param imp A function which is the implementation of the new method. The function must take at least two arguments—self and _cmd. * @param types An array of characters that describe the types of the arguments to the method. * * @return YES if the method was added successfully, otherwise NO * (for example, the class already contains a method implementation with that name). * * @note class_addMethod will add an override of a superclass's implementation, * but will not replace an existing implementation in this class. * To change an existing implementation, use method_setImplementation. */
大概的意思就是咱們能夠經過class_addMethod爲一個類添加方法(包括方法名稱(SEL)和方法的實現(IMP)),返回值爲BOOL類型,表示方法是否成功添加。spa
須要注意的地方是class_addMethod會添加一個覆蓋父類的實現,但不會取代原有類的實現。也就是說若是class_addMethod返回YES,說明子類中沒有方法originalSelector,經過class_addMethod爲其添加了方法originalSelector,並使其實現(IMP)爲咱們想要替換的實現線程
class_addMethod(class,originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
同時再將原有的實現(IMP)替換到swizzledMethod方法上指針
class_replaceMethod(class,swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
從而實現了方法的交換,而且未影響父類方法的實現。反之若是class_addMethod返回NO,說明子類中自己就具備方法originalSelector的實現,直接調用交換便可code
method_exchangeImplementations(originalMethod, swizzledMethod);
在Objective-C runtime會自動調用兩個類方法,分別爲+load與+ initialize。 +load 方法是在類被加載的時候調用的,也就是必定會被調用。 +initialize方法是在類或它的子類收到第一條消息以前被調用的,這裏所指的消息包括實例方法和類方法的調用。也就是說+initialize方法是以懶加載的方式被調用的,若是程序一直沒有給某個類或它的子類發送消息,那麼這個類的+initialize方法是永遠不會被調用的。 此外+load方法還有一個很是重要的特性,那就是子類、父類和分類中的+load方法的實現是被區別對待的。換句話說在 Objective-C runtime自動調用+load方法時,分類中的+load方法並不會對主類中的+load方法形成覆蓋。綜上所述,+load 方法是實現 Method Swizzling 邏輯的最佳「場所」 詳細請看:http://www.jianshu.com/p/872447c6dc3f
方法交換應該要線程安全,並且保證在任何狀況下(多線程環境,或者被其餘人手動再次調用+load方法)只交換一次,防止再次調用又將方法交換回來。除非只是臨時交換使用,在使用完成後又交換回來。 最經常使用的解決方案是在+load方法中使用dispatch_once來保證交換是安全的。以前有讀者反饋+load方法自己即爲線程安全,爲何仍需添加dispatch_once,其緣由就在於+load方法自己沒法保證其中代碼只被執行一次