原文地址函數
方法交換是OC運行時特徵之一,經過方法交換能夠hook方法,在實現一些需求時能夠達到事半功倍的效果,但使用不慎一樣可能致使不可思議的後果。在使用method swizzling前都應該理解如下注意點。spa
若是當前類未實現被交換的方法而父類實現了的狀況下,此時父類的實現會被交換,若此父類的多個繼承者都在交換時會致使方法被交換屢次而混亂,同時當調用父類的方法時會由於找不到而發生崩潰。
因此在交換前都應該先嚐試爲當前類添加被交換的函數的新的實現IMP,若是添加成功則說明類沒有實現被交換的方法,則只須要替代分類交換方法的實現爲原方法的實現,若是添加失敗,則原類中實現了被交換的方法,則能夠直接進行交換。code
這個過程主要涉及如下三個函數:繼承
BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types)
給類cls的SEL添加一個實現IMP, 返回YES則代表類cls並未實現此方法,返回NO則代表類已實現了此方法。注意:添加成功與否,徹底由該類自己來決定,與父類有無該方法無關。get
class_replaceMethod(Class _Nullable cls, SEL _Nonnull name, IMP _Nonnull imp, const char * _Nullable types)
替換類cls的SEL的函數實現爲impit
method_exchangeImplementations(Method _Nonnull m1, Method _Nonnull m2)
交換兩個方法的實現m1,m1io
最終的過程大體以下代碼:class
BOOL didAddMethod = class_addMethod(class,originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod)); if (didAddMethod) {//添加成功:則代表沒有實現IMP,將源SEL的IMP替換到交換SEL的IMP class_replaceMethod(class,swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod)); } else {//添加失敗:代表已實現,則能夠直接交換實現IMP method_exchangeImplementations(originalMethod, swizzledMethod); }
方法交換應當在調用前完成交換,+load方法發生在運行時初始化過程當中類被加載的時候調用,且每一個類被加載的時候只會調用一次load方法,調用的順序是父類、類、分類,且他們之間是相互獨立的,不會存在覆蓋的關係,因此放在+load方法中能夠確保在使用時已經完成交換。import
在第2點已經寫到,+load方法在類被加載的時候調用,且只會調用一次,那爲何還須要dispatch_once呢?這是爲了防止手動調用+load方法而致使反覆的被交換,由於這是存在可能的。coding
這個毫無疑問,由於分類的方法會覆蓋類中同名的方法,這樣會致使沒法預估的後果
不少狀況咱們不清楚被交換的的方法具體作了什麼內部邏輯,並且不少被交換的方法都是系統封裝的方法,因此爲了保證其邏輯性都應該在分類的交換方法中去調用原被交換方法,注意:調用時方法交換已經完成,在分類方法中應該調用分類方法自己才正確。
在每次的方法交換時都應該注意以上幾點,最終咱們能夠將其封裝到NSObject的分類中,方便在調用:
#import <Foundation/Foundation.h> #import <objc/runtime.h> @interface NSObject (Swizzling) + (void)methodSwizzlingWithOriginalSelector:(SEL)originalSelector swizzledSelector:(SEL)swizzledSelector; @end
#import "NSObject+Swizzling.h" @implementation NSObject (Swizzling) + (void)methodSwizzlingWithOriginalSelector:(SEL)originalSelector swizzledSelector:(SEL)swizzledSelector{ Class class = [self class]; //原有被交換方法 Method originalMethod = class_getInstanceMethod(class, originalSelector); //要交換的分類新方法 Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector); //避免交換到父類的方法,先嚐試添加被交換方法的新實現IMP BOOL didAddMethod = class_addMethod(class,originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod)); if (didAddMethod) {//添加成功:則代表沒有實現IMP,將源SEL的IMP替換到交換SEL的IMP class_replaceMethod(class,swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod)); } else {//添加失敗:代表已實現,則能夠直接交換實現IMP method_exchangeImplementations(originalMethod, swizzledMethod); } } @end