有時候?yàn)榱诉_(dá)到一些特殊的需求,我們會在運(yùn)行時期交換兩個方法的實(shí)現(xiàn),用我們自己的方法替換原始方法。在OC運(yùn)行時期,OC方法被表示為一個名為Method的C結(jié)構(gòu)typedef struct objc_method *Method,在runtime.h文件中,這個結(jié)構(gòu)體如下:
struct objc_method {
SEL _Nonnull method_name OBJC2_UNAVAILABLE;
char * _Nullable method_types OBJC2_UNAVAILABLE;
IMP _Nonnull method_imp OBJC2_UNAVAILABLE;
}
method_name是函數(shù)的選擇器,method_type是參數(shù)和返回值類型編碼的c字符串,method_imp是指向?qū)嶋H函數(shù)的函數(shù)指針??梢酝ㄟ^下面兩個方法訪問到object_method對象:
Method class_getClassMethod(Class aClass, SEL aSelector);
Method class_getInstanceMethod(Class aClass, SEL aSelector);
通過Method結(jié)構(gòu)體可以訪問更改底層的實(shí)現(xiàn)。實(shí)際上,我們完成swizzle操作就是將兩個方法的IMP進(jìn)行了一次交換。如何實(shí)現(xiàn)兩個方法IMP的交換呢?我將列舉兩種方式,后面將對兩種方式的進(jìn)行比較。一種簡單的實(shí)現(xiàn)方式就是通過:
void method_exchangeImplementations(Method m1, Method m2);
通過class_getClassMethod或(class_getInstanceMethod)獲取到method對象,通過調(diào)用method_exchangeImplementations就可實(shí)現(xiàn)兩個方法的IMP的交換,執(zhí)行完方法交換后,他們的objc_method結(jié)構(gòu)體發(fā)生如下變化:

在代碼調(diào)試的過程中,發(fā)現(xiàn)在一個問題,使用了method_exchangeImplementations實(shí)現(xiàn)方法交換之后,兩個方法的內(nèi)部_cmd與原來的方法名稱進(jìn)行了傳遞:
+ (void)method1{
// _cmd isEqueToString 'method2'
}
// at the catogary
+ (void)method2{
[self method2];
//_cmd isEqueToString 'method1'
}
顯而易見的是這種方式是有弊端的,如果一個函數(shù)實(shí)現(xiàn)依賴_cmd和方法名稱,這種方式會有想不到的后果產(chǎn)生。下面我們來看看第二種swizzle的實(shí)現(xiàn)。
第二種實(shí)現(xiàn)swizzle的方式是寫一個類似IMP的C函數(shù),IMP的定義如下:
void (*)(id,SEL,...)
類似IMP的C函數(shù):
void _swizzle_method2(self,_cmd){
}
我們可以將這個函數(shù)作為一個IMP來使用,通過method_setImplementation方法,將這個IMP替換掉原始方法的IMP:
IMP originImp = method_setImplementation(method1,(IMP)_swizzle_method2);
originImp是原始方法的IMP函數(shù)指針。如果想調(diào)用原始方法:
originImp(self,_cmd);
重要的代碼實(shí)現(xiàn):
//dic全局的字典對象,用來保存原來的方法實(shí)現(xiàn)
Method method = class_getInstanceMethod(self.class, @selector(method1));
IMP originImp = method_setImplementation(method, (IMP)_swizzle_method2);
if (!dic) {
dic = [NSMutableDictionary new];
}
[dic setObject:[NSValue valueWithPointer:originImp] forKey:@"method"];
//交換原始方法IMP的C函數(shù)
void _swizzle_method2(id self,SEL _cmd){
NSValue *value = [dic valueForKey:@"method"];
if (value) {
IMP originImp = [value pointerValue];
((void (*)(id,SEL))originImp)(self,_cmd);
}
}
//原來的方法
- (void)method1
{
//_cmd isEqueToString "method1"
}
這里我們可以發(fā)現(xiàn),原始方法method1現(xiàn)在函數(shù)內(nèi)的_cmd和原始方法名是相等的。因?yàn)镺C函數(shù)的調(diào)用都會返回兩個隱藏的參數(shù)(self, _cmd),如果我們忽略了這個特性,很可能在日常開發(fā)中,造成不必要的麻煩。很顯然第二種方式實(shí)現(xiàn)swizzle更優(yōu)雅。