關(guān)于Method swizzling的兩件事

1. 關(guān)于Method swizzling的兩種寫法。

簡(jiǎn)單實(shí)現(xiàn):

void simple_swizzle(Class class, SEL original, SEL swizzle) {
    Method originalMethod = class_getInstanceMethod(class, original);
    Method swizzleMethod = class_getInstanceMethod(class, swizzle);
    method_exchangeImplementations(originalMethod, swizzleMethod);
}

需要注意的是,class_getInstanceMethod如果沒(méi)在當(dāng)前類中查找到對(duì)應(yīng)方法那么就會(huì)在整個(gè)繼承體系中查找。如果明確知道originalMethod是當(dāng)前類實(shí)現(xiàn)的方法,這種方式可以正常工作。當(dāng)originalMethod是父類的方法時(shí),swizzle后父類調(diào)用此方法會(huì)因?yàn)檎也坏阶宇惖膶?shí)現(xiàn)然后報(bào)錯(cuò)。
如果originalMethod==nil說(shuō)明沒(méi)有找到這個(gè)方法,會(huì)導(dǎo)致swizzle失敗。

最佳實(shí)踐:

void best_swizzle(Class class, SEL original, SEL swizzle) {
    Method originalMethod = class_getInstanceMethod(class, original);
    Method swizzleMethod = class_getInstanceMethod(class, swizzle);
    //  嘗試向子類添加original方法,對(duì)應(yīng)的方法實(shí)現(xiàn)為swizzle后的實(shí)現(xiàn)。
    //  didAddMethod == NO時(shí),說(shuō)明當(dāng)前類存在originalMethod,處理方式和simple_swizzle一樣。
      // didAddMethod == YES時(shí),說(shuō)明當(dāng)前類沒(méi)有實(shí)現(xiàn)originalMethod,已成功添加
    BOOL didAddMethod = class_addMethod(class, original, method_getImplementation(swizzleMethod), method_getTypeEncoding(swizzleMethod));
    if (didAddMethod) {
//  這里將swizzle方法的實(shí)現(xiàn)replace成父類的實(shí)現(xiàn)
        class_replaceMethod(class, swizzle, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
    } else {
        method_exchangeImplementations(originalMethod, swizzleMethod);
    }
//  這里處理 originalMethod==nil 的情況,說(shuō)明之前在didAddMethod == YES的分支里repalce失敗。在這里直接將swizzleMethod的實(shí)現(xiàn)用一個(gè)block替換
    if (!originalMethod) {
        method_setImplementation(swizzleMethod, imp_implementationWithBlock(^(id self, SEL _cmd){
            NSLog(@"original method not implemented");
        }));
    }
}

一般情況下,如果明確需要swizzle的方法是當(dāng)前類的方法,那么直接用第一種方式即可,這種方式建議用在一些通用的,需要滿足高度容錯(cuò)度的場(chǎng)景,比如作為SDK的一部分發(fā)布出去的時(shí)候。

2. 關(guān)于多次swizzle后所有swizzle的方法是否都會(huì)被執(zhí)行的問(wèn)題。

一直有一個(gè)困惑,對(duì)同一個(gè)方法多次methods swizzing之后到底會(huì)發(fā)生什么,執(zhí)行的順序又是怎么樣的。趁著周末時(shí)間來(lái)測(cè)試驗(yàn)證一下。

創(chuàng)建Coder類,提供一個(gè)叫做coding的方法。

@interface Coder : NSObject
- (void)coding;
@end

@implementation Coder
- (void)coding {
    NSLog(@"\n %s", __PRETTY_FUNCTION__);
}
@end

創(chuàng)建用于swizzle的category,在category的load方法中連續(xù)swizzle5次。

@interface Coder (language)
@end
@implementation Coder (language)
+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        coder_bestSwizzle(self, @selector(coding), @selector(coding_objc));
        coder_bestSwizzle(self, @selector(coding), @selector(coding_kotlin));
        coder_bestSwizzle(self, @selector(coding), @selector(coding_cpp));
        coder_bestSwizzle(self, @selector(coding), @selector(coding_java));
        coder_bestSwizzle(self, @selector(coding), @selector(coding_swift));
    });
}
- (void)coding_objc {
    NSLog(@"\n %s", __PRETTY_FUNCTION__);
    [self coding_objc];
}
- (void)coding_kotlin {
    NSLog(@"\n %s", __PRETTY_FUNCTION__);
    [self coding_kotlin];
}
- (void)coding_cpp {
    NSLog(@"\n %s", __PRETTY_FUNCTION__);
    [self coding_cpp];
}
- (void)coding_java {
    NSLog(@"\n %s", __PRETTY_FUNCTION__);
    [self coding_java];
}
- (void)coding_swift {
    NSLog(@"\n %s", __PRETTY_FUNCTION__);
    [self coding_swift];
}
@end

新建coder對(duì)象,調(diào)用coding方法

- (void)coderTest {
    Coder *coder = Coder.new;
    [coder coding];
}

打印如下:

2018-03-17 20:17:30.093302+0800 MyOCDemoProject[36165:5131009] 
 -[Coder(objc) coding_swift]
2018-03-17 20:17:30.093449+0800 MyOCDemoProject[36165:5131009] 
 -[Coder(objc) coding_java]
2018-03-17 20:17:30.093568+0800 MyOCDemoProject[36165:5131009] 
 -[Coder(objc) coding_cpp]
2018-03-17 20:17:30.093675+0800 MyOCDemoProject[36165:5131009] 
 -[Coder(objc) coding_kotlin]
2018-03-17 20:17:30.093805+0800 MyOCDemoProject[36165:5131009] 
 -[Coder(objc) coding_objc]
2018-03-17 20:17:30.093915+0800 MyOCDemoProject[36165:5131009] 
 -[Coder coding]

如預(yù)期的所有方法都hook成功,并且按照FILO(first in last out)的順序調(diào)用了一遍。
那么這個(gè)順序?yàn)槭裁词沁@樣的呢?看下面的示意圖就知道了

  1. 開(kāi)始時(shí)的SEL和IMP的對(duì)應(yīng)關(guān)系


    FFC0929C-7071-458A-8998-F57481129BB7.png
  2. swizzle coding_objc & coding之后的對(duì)應(yīng)關(guān)系,IMP指針互換

  1. swizzle coding_kotlin & coding之后的對(duì)應(yīng)關(guān)系
    因?yàn)橹癱oding的IMP指針已經(jīng)指向了coding_objc的實(shí)現(xiàn),所以在method_exchangeImplementations時(shí)實(shí)際上交換的是coding方法的當(dāng)前IMP指針而不是原始方法實(shí)現(xiàn)對(duì)應(yīng)的IMP指針

    384BCB75-6289-4720-97B9-E3B9D833BB2C.png

  2. 后續(xù)的swizzle動(dòng)作也是如此,method_exchangeImplementations實(shí)際上互換的對(duì)象是前一次swizzle后coding方法對(duì)應(yīng)IMP指針而不是coding方法原始實(shí)現(xiàn)的IMP指針。
    最終SEL和IMP對(duì)應(yīng)關(guān)系如下圖所示,并且[coder coding]的整個(gè)執(zhí)行的路徑也在圖中用白線標(biāo)明。

    48DCBC28-73A7-4510-809D-A142072033EA.png

看到這里也就不難理解為什么最后會(huì)形成FILO的調(diào)用順序了。

需要注意的是:

  1. 正常來(lái)說(shuō)swizzle不會(huì)如本文那樣集中在同一個(gè)category的load方法中。方法的實(shí)際執(zhí)行順序就要具體類加載的順序。
  2. 在使用runtime替換系統(tǒng)的方法的實(shí)現(xiàn)的目的大多是為了hook系統(tǒng)方法方法,插入自己的實(shí)現(xiàn)。如果你的目的是hook,那么務(wù)必要記住在你替換的實(shí)現(xiàn)里,調(diào)用自己一次。否則就會(huì)導(dǎo)致上圖執(zhí)行路徑中,執(zhí)行到你的實(shí)現(xiàn)時(shí)從IMP連到SEL的線斷開(kāi),系統(tǒng)方法得不到調(diào)用。

參考文章:
Runtime中Swizzle時(shí)你可能沒(méi)注意到的問(wèn)題 - 簡(jiǎn)書(shū)
Objective-C Method Swizzling 的最佳實(shí)踐

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

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

  • 轉(zhuǎn)至元數(shù)據(jù)結(jié)尾創(chuàng)建: 董瀟偉,最新修改于: 十二月 23, 2016 轉(zhuǎn)至元數(shù)據(jù)起始第一章:isa和Class一....
    40c0490e5268閱讀 2,074評(píng)論 0 9
  • Runtime 3 Method Swizzling Objective-C Runtime(一) 簡(jiǎn)介 對(duì)象、類...
    liuyanhongwl閱讀 2,365評(píng)論 0 2
  • 導(dǎo)語(yǔ):Method Swizzling是Objective-C中運(yùn)行時(shí)中討論較多的內(nèi)容,本文主要介紹使用Metho...
    南華coder閱讀 4,851評(píng)論 5 26
  • 這篇文章完全是基于南峰子老師博客的轉(zhuǎn)載 這篇文章完全是基于南峰子老師博客的轉(zhuǎn)載 這篇文章完全是基于南峰子老師博客的...
    西木閱讀 30,893評(píng)論 33 466
  • 我是一片楓葉,我不會(huì)是落地的那些。我是一片楓葉,我會(huì)用力的撐開(kāi)所有的徑脈,去感受風(fēng)的擁抱。我是一片楓葉,我會(huì)隨風(fēng)去...
    歡樂(lè)的閱讀 268評(píng)論 0 0

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