方法交換是OC運行時特徵之一,經過方法交換能夠hook方法,在實現一些需求時能夠達到事半功倍的效果,但使用不慎一樣可能致使不可思議的後果。在使用method swizzling前都應該理解如下注意點。objective-c
若是當前類未實現被交換的方法而父類實現了的狀況下,此時父類的實現會被交換,若此父類的多個繼承者都在交換時會致使方法被交換屢次而混亂,同時當調用父類的方法時會由於找不到而發生崩潰。 因此在交換前都應該先嚐試爲當前類添加被交換的函數的新的實現IMP,若是添加成功則說明類沒有實現被交換的方法,則只須要替代分類交換方法的實現爲原方法的實現,若是添加失敗,則原類中實現了被交換的方法,則能夠直接進行交換。shell
這個過程主要涉及如下三個函數:bash
class_addMethod
函數
BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types)
複製代碼
給類cls的SEL添加一個實現IMP, 返回YES則代表類cls並未實現此方法,返回NO則代表類已實現了此方法。注意:添加成功與否,徹底由該類自己來決定,與父類有無該方法無關。學習
class_replaceMethod
class_replaceMethod(Class _Nullable cls, SEL _Nonnull name, IMP _Nonnull imp,
const char * _Nullable types)
複製代碼
替換類cls的SEL的函數實現爲impui
method_exchangeImplementations
method_exchangeImplementations(Method _Nonnull m1, Method _Nonnull m2)
複製代碼
交換兩個方法的實現m1,m1spa
最終的過程大體以下代碼:代理
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);
}
複製代碼
2.交換方法應在+load方法 方法交換應當在調用前完成交換,+load方法發生在運行時初始化過程當中類被加載的時候調用,且每一個類被加載的時候只會調用一次load方法,調用的順序是父類、類、分類,且他們之間是相互獨立的,不會存在覆蓋的關係,因此放在+load方法中能夠確保在使用時已經完成交換。code
3.交換方法應該放到dispatch_once中執行 在第2點已經寫到,+load方法在類被加載的時候調用,且只會調用一次,那爲何還須要dispatch_once呢?這是爲了防止手動調用+load方法而致使反覆的被交換,由於這是存在可能的。繼承
4.交換的分類方法應該添加自定義前綴,避免衝突 這個毫無疑問,由於分類的方法會覆蓋類中同名的方法,這樣會致使沒法預估的後果
5.交換的分類方法應調用原實現 不少狀況咱們不清楚被交換的的方法具體作了什麼內部邏輯,並且不少被交換的方法都是系統封裝的方法,因此爲了保證其邏輯性都應該在分類的交換方法中去調用原被交換方法,注意:調用時方法交換已經完成,在分類方法中應該調用分類方法自己才正確。
在每次的方法交換時都應該注意以上幾點,最終咱們能夠將其封裝到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
複製代碼
開發中,咱們有時會使用runtime進行swizzle方法(若是不知道swizzle請看這裏連接)。多數時候咱們會這樣寫,swizzle第一種寫法:
Method originalMethod = class_getInstanceMethod(aClass, originalSel);
Method swizzleMethod = class_getInstanceMethod(aClass, swizzleSel);
method_exchangeImplementations(originalMethod, swizzleMethod);
複製代碼
或者是下面這種方式,swizzle第二種寫法:
Method originalMethod = class_getInstanceMethod(aClass, originalSel);
Method swizzleMethod = class_getInstanceMethod(aClass, swizzleSel);
BOOL didAddMethod = class_addMethod(aClass, originalSel, method_getImplementation(swizzleMethod), method_getTypeEncoding(swizzleMethod));
if (didAddMethod) {
class_replaceMethod(aClass, swizzleSel, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
}else{
method_exchangeImplementations(originalMethod, swizzleMethod);
}
複製代碼
網上資料查找時發現第二種方式網上有稱呼爲最佳的swizzle方式,具體可見資料(最佳實踐相關資料).
上面兩種方式具體哪一種好呢,我下不定論,由於在使用過程當中,發現他們各自都有本身的問題(將在下面指出),具體還得看本身需求以爲哪一種好,下面將解釋這兩種方式的一些問題,本文代碼見代碼地址。
先說說第一種狀況(當swizzle一個自身沒有實現而父類實現了的方法時)下兩種方式比較:
首先咱們創建兩個類:father類以及繼承fahter類的son類
@interface Father : NSObject
@end
@implementation Father
-(void)work{
NSLog(@"父親得賺錢養家????");
}
@end
@interface Son : Father
@end
@implementation Son
@end
複製代碼
而後新建Son分類:
@implementation Son (Swizzle)
BOOL simple_Swizzle(Class aClass, SEL originalSel,SEL swizzleSel){
Method originalMethod = class_getInstanceMethod(aClass, originalSel);
Method swizzleMethod = class_getInstanceMethod(aClass, swizzleSel);
method_exchangeImplementations(originalMethod, swizzleMethod);
return YES;
}
BOOL best_Swizzle(Class aClass, SEL originalSel,SEL swizzleSel){
Method originalMethod = class_getInstanceMethod(aClass, originalSel);
Method swizzleMethod = class_getInstanceMethod(aClass, swizzleSel);
BOOL didAddMethod = class_addMethod(aClass, originalSel, method_getImplementation(swizzleMethod), method_getTypeEncoding(swizzleMethod));
if (didAddMethod) {
class_replaceMethod(aClass, swizzleSel, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
}else{
method_exchangeImplementations(originalMethod, swizzleMethod);
}
return YES;
}
@end
複製代碼
這裏咱們將第一種方式稱爲simple_Swizzle
,第二種方式爲best_Swizzle
。
狀況一:子類裏swizzle一個自身沒有實現而父類實現了的方法。
咱們在分類中添加一個本身的work方法準備swizzle
@implementation Son (Swizzle)
-(void)son_work{
[self son_work];
NSLog(@"son分類裏的son_work");
}
@end
複製代碼
而後再load方法中進行替換,咱們先使用simple_Swizzle。
+(void)load{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
simple_Swizzle([self class], NSSelectorFromString(@"work"), @selector(son_work));
};
}
複製代碼
嗯,好了,運行程序後,調用以下代碼
Son *son = [Son new];
[son work];
複製代碼
控制檯打印以下:
2017-06-18 16:11:20.443302 runtime學習[45823:16276777] 父親得賺錢養家????
2017-06-18 16:11:20.448797 runtime學習[45823:16276777] son分類裏的son_work
複製代碼
也許這裏你已經發現問題,若是沒有咱們繼續,再調用下面代碼:
Father *aFather = [Father new];
[aFather work];
複製代碼
程序崩潰了,控制檯輸出:
[Father son_work]: unrecognized selector sent to instance 0x1700162f0' 複製代碼
what?(黑人問號?),什麼狀況!![aFather work]裏調用了son_work。
原來,剛剛的simple_Swizzle將子類的son_work的IMP替換給類父類的work方法,致使父類方法work裏有[self son_work];而父類並無這樣的方法因此over了。
具體爲何會替換掉父類的方法,是由於class_getInstanceMethod方法在找方法時會從本身類到父類到根類一直查找下去,一直到根類爲止,因此上面在查找work方法時就找到father類裏了。這就是simple_Swizzle的一個問題。
如今咱們使用simple_Swizzle來進行替換。
best_Swizzle([self class], NSSelectorFromString(@"work"), @selector(son_work));複製代碼 而後
Son *son = [Son new];
[son work];
複製代碼
控制檯打印以下:
2017-06-18 16:27:43.491944 runtime學習[45833:16282207] 父親得賺錢養家????
2017-06-18 16:27:43.493439 runtime學習[45833:16282207] son分類裏的son_work
複製代碼
沒問題,接着:
Father *aFather = [Father new];
[aFather work];
複製代碼
控制檯打印以下:
2017-06-18 16:30:39.622756 runtime學習[45836:16282868] 父親得賺錢養家????
複製代碼
能夠看到這時父類沒有調用son_work。由於best_Swizzle在進行swizzle時會先嚐試給本身添加work方法方法實現
BOOL didAddMethod = class_addMethod(aClass, originalSel, method_getImplementation(swizzleMethod), method_getTypeEncoding(swizzleMethod));
複製代碼
若是son類沒有實現work方式時,class_addMethod就會成功添加一個新的屬於son本身的work方法,同時將原本要swizzle的方法的實現直接複製進work裏,而後再將父類的IMP給swizzle。若是son已經已經實現了則會添加失敗,直接進行swizzle具體可見資料(),邏輯爲下面代碼。
if (didAddMethod) {
class_replaceMethod(aClass, swizzleSel, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
}else{
method_exchangeImplementations(originalMethod, swizzleMethod);
}
複製代碼
這時simple_Swizzle會有問題,best_Swizzle沒有問題。
下面咱們來講另一種狀況(swizzle一個子類到父類到根類都沒有實現過的方法):
咱們給son分類添加一個son_Cry方法:
-(void)son_Cry{
NSLog(@"我是son分類,WZ_Cry方法");
[self son_Cry];
}
複製代碼
同時聲明一個沒有實現的方法cry;
@interface Son : Father
1
-(void)cry;
@end
複製代碼
而後先用simple_Swizzle替換:
simple_Swizzle([self class], @selector(cry), @selector(son_Cry));
複製代碼
接着調用cry方法:
Son *son = [Son new];
[son cry];
複製代碼
控制檯打印:
[Son cry]: unrecognized selector sent to instance 0x170018900' 複製代碼
嗯,調用失敗,正常由於都沒有實現如何swizzle。而後用咱們用回自動添加一個實現的best_Swizzle方式,控制檯打印以下:
2017-06-18 16:59:33.815223 runtime學習[45845:16289311] 我是son分類,WZ_Cry方法
2017-06-18 16:59:33.815243 runtime學習[45845:16289311] 我是son分類,WZ_Cry方法
2017-06-18 16:59:33.815319 runtime學習[45845:16289311] 我是son分類,WZ_Cry方法
複製代碼
...此處省略無數次上面的log打印
再一次(what?黑人問號?),程序死循環了...
問題出在這兩行代碼:
//由於cry方法沒有實現過,class_getInstanceMethod沒法找到該方法,因此originalMethod爲nil。
Method originalMethod = class_getInstanceMethod(aClass, originalSel);
複製代碼
//當originalMethod爲nil時這裏class_replaceMethod將不作替換,因此swizzleSel方法裏的實現仍是本身原來的實現。
class_replaceMethod(aClass, swizzleSel, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
複製代碼
因此最終致使在下面方法裏重複調用本身。
-(void)son_Cry{
NSLog(@"我是son分類,WZ_Cry方法");
[self son_Cry];
}
複製代碼
這裏就是best_Swizzle的一個問題,固然這個問題不多出現,由於咱們通常swizzle時都是swizzle一個已知的方法,因此通常都有方法實現。
可是也不是不會出現這種問題,這裏我在一次進行swizzle一個類實現的協議方法時出現了,好比AppDelegate類裏的協議,我想在某個代理回調裏插入一段本身的邏輯,又不想對已有的項目裏直接加入,我想它時能夠直接拿到另一個項目也能夠直接用的,因此我建了一個分類swizzle協議方法,若是我直接swizzle一個沒有實現過的協議方法,就會出現死循環。
具體可見demo
裏的解決方法是在originalMethod爲nil時,替換後將swizzleSel複製一個不作任何事的空實現,代碼以下:
if (!originalMethod)
{
class_addMethod(aClass, originalSel, method_getImplementation(swizzleMethod), method_getTypeEncoding(swizzleMethod));
method_setImplementation(swizzleMethod, imp_implementationWithBlock(^(id self, SEL _cmd){ }));
}
複製代碼
喝口水 總結:
對於simple_Swizzle和best_Swizzle的選擇沒有具體要求,你們結合本身的場景使用,我通常直接用simple_Swizzle,由於我知道這個方法必定有,因此能簡單就粗暴吧。