iOS底層學(xué)習(xí) -objc_msgSend慢速查找流程分析

上一篇中,分析了快速查找流程,如果快速查不到,則需要進(jìn)入慢速查找流程,核心方法_lookUpImpOrForward。

慢速查找底層源碼

_lookUpImpOrForward慢速查找流程

IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)
{
    // 定義的消息轉(zhuǎn)發(fā)
    const IMP forward_imp = (IMP)_objc_msgForward_impcache; 
    IMP imp = nil;
    Class curClass;

    runtimeLock.assertUnlocked();

    // 快速查找,如果找到則直接返回imp
    //目的:防止多線程操作時(shí),剛好調(diào)用函數(shù),此時(shí)緩存進(jìn)來了
    if (fastpath(behavior & LOOKUP_CACHE)) { 
        imp = cache_getImp(cls, sel);
        if (imp) goto done_nolock;
    }
    
    //加鎖,目的是保證讀取的線程安全
    runtimeLock.lock();
    
    //判斷是否是一個(gè)已知的類:判斷當(dāng)前類是否是已經(jīng)被認(rèn)可的類,即已經(jīng)加載的類
    checkIsKnownClass(cls); 
    
    //判斷類是否實(shí)現(xiàn),如果沒有,需要先實(shí)現(xiàn),此時(shí)的目的是為了確定父類鏈,方法后續(xù)的循環(huán)
    if (slowpath(!cls->isRealized())) { 
        cls = realizeClassMaybeSwiftAndLeaveLocked(cls, runtimeLock);
    }

    //判斷類是否初始化,如果沒有,需要先初始化
    if (slowpath((behavior & LOOKUP_INITIALIZE) && !cls->isInitialized())) { 
        cls = initializeAndLeaveLocked(cls, inst, runtimeLock);
    }

    runtimeLock.assertLocked();
    curClass = cls;

    //----查找類的緩存
    
    // unreasonableClassCount -- 表示類的迭代的上限
    //(猜測這里遞歸的原因是attempts在第一次循環(huán)時(shí)作了減一操作,然后再次循環(huán)時(shí),仍在上限的范圍內(nèi),所以可以繼續(xù)遞歸)
    for (unsigned attempts = unreasonableClassCount();;) { 
        //---當(dāng)前類方法列表(采用二分查找算法),如果找到,則返回,將方法緩存到cache中
        Method meth = getMethodNoSuper_nolock(curClass, sel);
        if (meth) {
            imp = meth->imp;
            goto done;
        }
        //當(dāng)前類 = 當(dāng)前類的父類,并判斷父類是否為nil
        if (slowpath((curClass = curClass->superclass) == nil)) {
            //--未找到方法實(shí)現(xiàn),方法解析器也不行,使用轉(zhuǎn)發(fā)
            imp = forward_imp;
            break;
        }

        // 如果父類鏈中存在循環(huán),則停止
        if (slowpath(--attempts == 0)) {
            _objc_fatal("Memory corruption in class list.");
        }

        // --父類緩存
        imp = cache_getImp(curClass, sel);
        if (slowpath(imp == forward_imp)) { 
            // 如果在父類中找到了forward,則停止查找,且不緩存,首先調(diào)用此類的方法解析器
            break;
        }
        if (fastpath(imp)) {
            //如果在父類中,找到了此方法,將其存儲(chǔ)到cache中
            goto done;
        }
    }

    //沒有找到方法實(shí)現(xiàn),嘗試一次方法解析

    if (slowpath(behavior & LOOKUP_RESOLVER)) {
        //動(dòng)態(tài)方法決議的控制條件,表示流程只走一次
        behavior ^= LOOKUP_RESOLVER; 
        return resolveMethod_locked(inst, sel, cls, behavior);
    }

 done:
    //存儲(chǔ)到緩存
    log_and_fill_cache(cls, imp, sel, inst, curClass); 
    //解鎖
    runtimeLock.unlock();
 done_nolock:
    if (slowpath((behavior & LOOKUP_NIL) && imp == forward_imp)) {
        return nil;
    }
    return imp;
}

我們來分析下整個(gè)慢速查找流程:
首先在cache緩存中進(jìn)行查找,即快速查找,找到則直接返回imp。

接下來判讀cls,先判斷是否是已知類,如果不是則報(bào)錯(cuò),再判斷類是否實(shí)現(xiàn),如果沒有,則需要先實(shí)現(xiàn),確定其父親鏈,最后判斷是否初始化,如果沒有,則初始化。

判斷完成后,就會(huì)開啟一個(gè)for循環(huán),通過方法名和當(dāng)前類名去對(duì)應(yīng)的方法列表查詢。這是一個(gè)死循環(huán),循環(huán)退出的條件就是(curClass->superclass) == nil。每次循環(huán)如果沒有結(jié)果curClass的指針就會(huì)指向父類,直到為nil跳出循環(huán)。
其中使用了一個(gè)二分查找算法,如果查找到,則進(jìn)入cache寫入流程,并返回imp,否則返回nil。然后是多個(gè)判斷,第一個(gè)判斷是將當(dāng)前cls賦值為父類,判斷如果父類等于nil,則終止遞歸,進(jìn)入消息轉(zhuǎn)發(fā)。第二個(gè)判斷如果父類鏈中存在循環(huán),也報(bào)錯(cuò),終止循環(huán)。上述條件全都滿足時(shí),就會(huì)在父類緩存中查找方法,如果未找到,則直接返回nil,繼續(xù)循環(huán)查找,如果找到,則直接返回imp,執(zhí)行cache寫入流程。

最后判斷是否執(zhí)行過動(dòng)態(tài)方法解析,如果沒有,執(zhí)行動(dòng)態(tài)方法解析,如果執(zhí)行過一次動(dòng)態(tài)方法解析,則走到消息轉(zhuǎn)發(fā)流程。

以上就是整個(gè)慢速查找流程。

getMethodNoSuper_nolock:二分查找方法列表

核心源碼

ALWAYS_INLINE static method_t *
findMethodInSortedMethodList(SEL key, const method_list_t *list)
{
    const method_t * const first = &list->first;
    const method_t *base = first;
    const method_t *probe;
    uintptr_t keyValue = (uintptr_t)key;
    uint32_t count;
    
    for (count = list->count; count != 0; count >>= 1) {
        probe = base + (count >> 1);
        
        uintptr_t probeValue = (uintptr_t)probe->name;
        
        if (keyValue == probeValue) {
            while (probe > first && keyValue == (uintptr_t)probe[-1].name) {
                probe--;
            }
            return (method_t *)probe;
        }
        
        if (keyValue > probeValue) {
            base = probe + 1;
            count--;
        }
    }
    return nil;
}

具體可分為以下幾步

  1. base指向列表第一個(gè)元素。
  2. 獲取列表中間元素probe,將這個(gè)元素的方法名并轉(zhuǎn)換成uintptr_t類型。比較keyValue和probeValue的大小。
  3. 如果相等,說明找到了對(duì)應(yīng)的方法,然后進(jìn)一步循環(huán)判斷這個(gè)位置的方法的SEL是否和前一個(gè)位置方法的SEL相等,相等則可能是有分類的同名方法,指針前移,然后循環(huán),直到離開循環(huán)條件,返回結(jié)果。
  4. 如果keyValue大于probeValue的值,base = probe + 1使其重新指向減半后的列表第一個(gè)元素,并且count --,然后重復(fù)步驟2.
  5. 如果keyValue小于probeValue的值,重復(fù)步驟2.

當(dāng)完成了方法的查找之后,就會(huì)直接goto done,這一步會(huì)對(duì)查找到的方法做一個(gè)方法緩存的操作,內(nèi)部會(huì)調(diào)用cache_t::insert(),方便下次調(diào)用的時(shí)候,消息發(fā)送就會(huì)通過緩存查找,走快速查找流程。

當(dāng)getMethodNoSuper_nolock()沒有在自己類中找到方法的時(shí)候
就會(huì)調(diào)用if (slowpath((curClass = curClass->superclass) == nil))
把當(dāng)前類指針curClass指向自己的父類,然開始從父類緩存中開始查找cache_getImp(),如果找到了就返回,否則就會(huì)回到頂部遞歸循環(huán)。如果一直沒有查詢到結(jié)果則會(huì)進(jìn)入下一個(gè)流程——消息的動(dòng)態(tài)轉(zhuǎn)發(fā)。

總的來說,二分查找算法的核心思想,是從第一次查找開始,每次都取中間位置,與想查找的key的value值作比較,如果相等,則需要排除分類方法,然后將查詢到的位置的方法實(shí)現(xiàn)返回,如果不相等,則需要繼續(xù)二分查找,如果循環(huán)至count = 0還是沒有找到,則直接返回nil。

?著作權(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)容