12- Runtime基礎(chǔ)使用場景-攔截替換方法(class_addMethod ,class_replaceMethod和method_exchangeImplementations)

更新

針對本文和評論區(qū)的大家的疑惑,我統(tǒng)一寫到了這篇文章中,解釋了class_addMethod ,class_replaceMethodmethod_exchangeImplementations這三個方法的作用和為什么要用它們,大家可以看下:

《詳講Runtime方法交換(class_addMethod ,class_replaceMethod和method_exchangeImplementations)》


前話

這幾天在系統(tǒng)的學(xué)習(xí) runtime,在學(xué)習(xí) runtime 的基礎(chǔ)使用案例中,"方法替換"這種使用情況下,發(fā)現(xiàn)有兩種寫法. 其實也不是兩種寫法,準確的來說一種是比較嚴謹?shù)?另一種則沒有那么嚴謹.

發(fā)現(xiàn)這兩種寫法的差異后,我主要集中在下列:

  • class_addMethod
  • class_replaceMethod
  • method_exchangeImplementations

哪個方法的具體作用.

下面,這篇文章就這兩種寫法和上述三種方法的區(qū)別.

第一種寫法

《OC最實用的runtime總結(jié),面試、工作你看我就足夠了!》的時候,它里邊的寫法是簡單的獲取到被替換和替換方法的Method.然后直接使用method_exchangeImplementations進行方法的替換. 最開始使用的時候,因為測試范例比較簡單,所以并沒有發(fā)現(xiàn)這樣寫的弊端.但是確實能夠?qū)崿F(xiàn)方法替換的效果. 代碼如下:

+(void)load{
    //獲取兩個類的方法
    Method m1 = class_getClassMethod([UIImage class], @selector(imageNamed:));
    Method m2 = class_getClassMethod([UIImage class], @selector(ll_imageName:));
    //開始交換方法實現(xiàn)
    method_exchangeImplementations(m1, m2);
}

在后來看到《runtime詳解》的時候,發(fā)現(xiàn)作者的寫法并不是這樣,雖然作者添加少量注釋,但是愚鈍的我還沒有想清楚,這也是這篇文章的初衷,也是下一小結(jié)的由來.

第二種寫法

上一節(jié)的這種情況雖然能夠?qū)崿F(xiàn)我們想要的效果.但是我們有沒有想過這種情況:

" 周全起見,有兩種情況要考慮一下。第一種情況是要復(fù)寫的方法(overridden)并沒有在目標類中實現(xiàn)(notimplemented),而是在其父類中實現(xiàn)了。第二種情況是這個方法已經(jīng)存在于目標類中(does existin the class itself)。這兩種情況要區(qū)別對待。 (譯注: 這個地方有點要明確一下,它的目的是為了使用一個重寫的方法替換掉原來的方法。但重寫的方法可能是在父類中重寫的,也可能是在子類中重寫的。) 對于第一種情況,應(yīng)當(dāng)先在目標類增加一個新的實現(xiàn)方法(override),然后將復(fù)寫的方法替換為原先(的實現(xiàn)(original one)。 對于第二情況(在目標類重寫的方法)。這時可以通過method_exchangeImplementations來完成交換."

---- 以上來自:《Objective-C的方法替換》

+(void)load{
    NSString *className = NSStringFromClass(self.class);
    NSLog(@"classname %@", className);
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        //要特別注意你替換的方法到底是哪個性質(zhì)的方法
        // When swizzling a Instance method, use the following:
        //        Class class = [self class];

        // When swizzling a class method, use the following:
        Class class = object_getClass((id)self);

        SEL originalSelector = @selector(systemMethod_PrintLog);
        SEL swizzledSelector = @selector(ll_imageName);

        Method originalMethod = class_getInstanceMethod(class, originalSelector);
        Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);

        BOOL didAddMethod =
        class_addMethod(class,
                        originalSelector,
                        method_getImplementation(swizzledMethod),
                        method_getTypeEncoding(swizzledMethod));

        if (didAddMethod) {
            class_replaceMethod(class,
                                swizzledSelector,
                                method_getImplementation(originalMethod),
                                method_getTypeEncoding(originalMethod));
        } else {
            method_exchangeImplementations(originalMethod, swizzledMethod);
        }
    });
}

解析:

上面提到的:

dispatch_once這里不是“單例”,是保證方法替換只執(zhí)行一次.

說明:

systemMethod_PrintLog:被替換方法ll_imageName:替換方法

class_addMethod:如果發(fā)現(xiàn)方法已經(jīng)存在,會失敗返回,也可以用來做檢查用,我們這里是為了避免源方法沒有實現(xiàn)的情況;如果方法沒有存在,我們則先嘗試添加被替換的方法的實現(xiàn)

1.如果返回成功:則說明被替換方法沒有存在.也就是被替換的方法沒有被實現(xiàn),我們需要先把這個方法實現(xiàn),然后再執(zhí)行我們想要的效果,用我們自定義的方法去替換被替換的方法. 這里使用到的是class_replaceMethod這個方法. class_replaceMethod本身會嘗試調(diào)用class_addMethodmethod_setImplementation,所以直接調(diào)用class_replaceMethod就可以了)

2.如果返回失敗:則說明被替換方法已經(jīng)存在.直接將兩個方法的實現(xiàn)交換即

另外:

  • 我們可以利用 method_exchangeImplementations 來交換2個方法中的IMP
  • 我們可以利用 class_replaceMethod 來修改類
  • 我們可以利用 method_setImplementation 來直接設(shè)置某個方法的IMP

其實我們?nèi)绻?研究過 AFN 代碼的話,會發(fā)現(xiàn), AFN 就是第二種寫法.在AFURLSessionManager.m的第296行:

static inline void af_swizzleSelector(Class class, SEL originalSelector, SEL swizzledSelector) {
    Method originalMethod = class_getInstanceMethod(class, originalSelector);
    Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
    if (class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod))) {
        class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
    } else {
        method_exchangeImplementations(originalMethod, swizzledMethod);
    }

}

詳盡的代碼請查看 Demo.

下載地址

具體的 Demo 代碼可以在我的 GitHub 上找到 Demo地址

其它

關(guān)于 load 的調(diào)用次數(shù)問題,大家可以查看這兩篇文章.+(void)load和+(void)initialize可當(dāng)做普通類方法(Class Method)調(diào)用的.《NSObject的load和initialize方法!》《Objective C類方法load和initialize的區(qū)別》

參考文章

  1. 《OC最實用的runtime總結(jié),面試、工作你看我就足夠了!》
  2. 《Objective-C的方法替換》

交流

希望能和大家交流技術(shù)

我的博客地址: http://www.lilongcnc.cc/


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

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

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