深入剖析Objective-C中的Swizzle

有時候?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ā)生如下變化:

圖1.png

在代碼調(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)雅。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容