runtime方法交換 為啥要先class_addMethod呢

方法交換
void swizzleMethod(Class class, SEL originalSelector, SEL swizzledSelector){
Method originalMethod = class_getInstanceMethod(class, originalSelector);
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);

//底層調(diào)用了class_addMethod 最有一個(gè)參數(shù)yes首先先查看本類是不是有原始方法,如果沒有創(chuàng)建一個(gè)方法列表并且把方法屬性添加進(jìn)去,后邊有詳細(xì)說明
BOOL didAddMethod = class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));

if (didAddMethod) { 
 //注意 class_replaceMethod里 方法也調(diào)用了 class_addMethod方法 最有一個(gè)參數(shù)NO
    IMP rel =  class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
} else {
    method_exchangeImplementations(originalMethod, swizzledMethod);
 }
}

我們的前提是swizzledSelector這個(gè)SEL是真實(shí)存在于本class中的
1.originalSelector有可能存在父類(class_getInstanceMethod是按照繼承鏈查找方法的)
2.如果originalSelector為父類方法,而本類沒有,直接交換帶來的后果就是影響了所有父類的事例對(duì)象的方法實(shí)現(xiàn),后果就不可控了.
3.而如果originalSelector為父類方法,先調(diào)用class_addMethod將其添加到本類中,并且其方法實(shí)現(xiàn)為swizzledSelector的,這里相當(dāng)于在添加的時(shí)候同步進(jìn)行了方法實(shí)現(xiàn)交換.
4.如果添加成功,則說明originalSelector本來不存在于本class,那么剩下的就是將swizzledSelector所對(duì)應(yīng)的方法實(shí)現(xiàn)替換成originalSelector的方法實(shí)現(xiàn)即可.
5.如果添加不成功,則說明originalSelector存在于本class,那么這時(shí)交換兩個(gè)方法的實(shí)現(xiàn)是完全基于本類的,所以可控、安全.

runtime源碼解釋了上述說明:

class_getInstanceMethod
Method class_getInstanceMethod(Class cls, SEL sel)
{
 ·····
 --重點(diǎn)--
 return _class_getMethod(cls, sel);
}
static Method _class_getMethod(Class cls, SEL sel)
{
 ·····
 --重點(diǎn)--
mutex_locker_t lock(runtimeLock);
return getMethod_nolock(cls, sel);
}
最終調(diào)用到:
static method_t *
getMethod_nolock(Class cls, SEL sel)
{
  method_t *m = nil;
  重點(diǎn)在這!!!! class_getInstanceMethod方法會(huì)沿著繼承鏈往上找, 
  如果本類沒有,父類有,則找到的是父類方法
  while (cls  &&  ((m = getMethodNoSuper_nolock(cls, sel))) == nil) {
    cls = cls->superclass;
}
return m;
}
class_addMethod
BOOL 
class_addMethod(Class cls, SEL name, IMP imp, const char *types)
{
if (!cls) return NO;

mutex_locker_t lock(runtimeLock);
return ! addMethod(cls, name, imp, types ?: "", NO);
}

static IMP 
 addMethod(Class cls, SEL name, IMP imp, const char *types, bool replace)
  {
IMP result = nil;
// 只留核心代碼
method_t *m;
// 查看本類是否有此方法,這里也是個(gè)重點(diǎn),NoSuper也就是不會(huì)沿著繼承鏈查找,只在本類進(jìn)行
if ((m = getMethodNoSuper_nolock(cls, name))) {
    // already exists
    // 如果有,但是不替換
    if (!replace) {
        // 直接獲取結(jié)果
        result = m->imp;
    } else {
        // 如果需要替換,則直接將實(shí)現(xiàn)覆蓋
        result = _method_setImplementation(cls, m, imp);
    }
} else {
    // 如果本類中不存在此方法 
    // 創(chuàng)建一個(gè)新的方法列表,并把此方法屬性填充進(jìn)去
    // fixme optimize
    method_list_t *newlist;
    newlist = (method_list_t *)calloc(sizeof(*newlist), 1);
    newlist->entsizeAndFlags = 
        (uint32_t)sizeof(method_t) | fixed_up_method_list;
    newlist->count = 1;
    newlist->first.name = name;
    newlist->first.types = strdupIfMutable(types);
    newlist->first.imp = imp;
    
    //  對(duì)新的方法列表的添加做準(zhǔn)備
    //  1, 注冊(cè)方法名
    //  2, 對(duì)方法列表進(jìn)行排序
    //  3, 標(biāo)記此方法列表唯一,并且有序
    prepareMethodLists(cls, &newlist, 1, NO, NO);
    
    // 添加到現(xiàn)有的方法列表之后
    cls->data()->methods.attachLists(&newlist, 1);
    
    //  刷新緩存
    flushCaches(cls);

    result = nil;
}

return result;
}
class_replaceMethod
IMP 
class_replaceMethod(Class cls, SEL name, IMP imp, const char *types)
{
if (!cls) return nil;

mutex_locker_t lock(runtimeLock);
// 它的核心就是調(diào)用了addMethod 最后一個(gè)參數(shù)“YES”的意思如果class存在SEL為name的方法,則替換它的實(shí)現(xiàn).這個(gè)過程在Step1的源碼解析中有.
return addMethod(cls, name, imp, types ?: "", YES);
}
method_exchangeImplementations
 void method_exchangeImplementations(Method m1, Method m2)
{
// 判空 
if (!m1  ||  !m2) return;

// 上鎖
mutex_locker_t lock(runtimeLock);

// 經(jīng)典指針指向交換
IMP m1_imp = m1->imp;
m1->imp = m2->imp;
m2->imp = m1_imp;

// 修復(fù)其方法在外部已知的類的生成列表
// RR/AWZ updates are slow because class is unknown ()
// Cache updates are slow because class is unknown
// fixme build list of classes whose Methods are known externally?

// 刷新緩存
flushCaches(nil);

// 看方法名是根據(jù)方法變更 適配class 中 custom flag狀態(tài)
// 內(nèi)部方法進(jìn)行了RR/AWZ/CORE 等方法的掃描
adjustCustomFlagsForMethodChange(nil, m1);
adjustCustomFlagsForMethodChange(nil, m2);
}
 static void
 adjustCustomFlagsForMethodChange(Class cls, method_t *meth)
{
objc::AWZScanner::scanChangedMethod(cls, meth);
objc::RRScanner::scanChangedMethod(cls, meth);
objc::CoreScanner::scanChangedMethod(cls, meth);
}

總結(jié)

1.class_addMethod的作用本質(zhì)是防止產(chǎn)生父類和子類方法實(shí)現(xiàn)的交換,這樣會(huì)打破父類原有的生態(tài),導(dǎo)致不可預(yù)計(jì)的問題.
2.添加新方法的時(shí)候會(huì)創(chuàng)建新的方法列表,并且對(duì)其進(jìn)行注冊(cè)等準(zhǔn)備工作,添加到現(xiàn)有的方法列表之后
3.交換方法實(shí)現(xiàn),添加方法都會(huì)對(duì)緩存進(jìn)行一次清空,這種動(dòng)作是比較耗費(fèi)資源的,如果緩存比較大,會(huì)大幅降低緩存命中,降低效率(class_addMethod 中 flushCaches(cls);)
4.方法交換后,runtime會(huì)遍歷NSObject和元類,查看是否有和這兩個(gè)方法相關(guān)聯(lián)的地方,并進(jìn)行setNSObjectSwizzled設(shè)置,標(biāo)記本類中是否有Swizzled操作(adjustCustomFlagsForMethodChange中scanChangedMethod底層)objc源碼
僅作為學(xué)習(xí)記錄

最后編輯于
?著作權(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),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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