iOS底層 - 方法慢速查找流程分析

iOS開發(fā)底層探究之路

慢速查找匯編分析

在上篇文章iOS底層 - objc_msgSend快速查找流程分析中我們分析了通過匯編進(jìn)行的objc_msgSend快速查找流程:CacheLookup 匯編方法在cache緩存中沒找到,CheckMissJumpMiss 都會跳到__objc_msgSend_uncached 匯編方法。

STATIC_ENTRY __objc_msgSend_uncached
UNWIND __objc_msgSend_uncached, FrameWithNoSaves
// THIS IS NOT A CALLABLE C FUNCTION
// Out-of-band p16 is the class to search
    
MethodTableLookup
TailCallFunctionPointer x17

END_ENTRY __objc_msgSend_uncached

此方法中最重要的就是MethodTableLookup查找方法列表函數(shù)。

.macro MethodTableLookup
    
    // push frame
    SignLR
    stp fp, lr, [sp, #-16]!
    mov fp, sp

    // save parameter registers: x0..x8, q0..q7
    sub sp, sp, #(10*8 + 8*16)
    stp q0, q1, [sp, #(0*16)]
    stp q2, q3, [sp, #(2*16)]
    stp q4, q5, [sp, #(4*16)]
    stp q6, q7, [sp, #(6*16)]
    stp x0, x1, [sp, #(8*16+0*8)]
    stp x2, x3, [sp, #(8*16+2*8)]
    stp x4, x5, [sp, #(8*16+4*8)]
    stp x6, x7, [sp, #(8*16+6*8)]
    str x8,     [sp, #(8*16+8*8)]

    // lookUpImpOrForward(obj, sel, cls, LOOKUP_INITIALIZE | LOOKUP_RESOLVER)
    // receiver and selector already in x0 and x1
    mov x2, x16
    mov x3, #3
    bl  _lookUpImpOrForward   // 跳轉(zhuǎn)到此方法

    // IMP in x0
    mov x17, x0
    
    // restore registers and return
    ldp q0, q1, [sp, #(0*16)]
    ldp q2, q3, [sp, #(2*16)]
    ldp q4, q5, [sp, #(4*16)]
    ldp q6, q7, [sp, #(6*16)]
    ldp x0, x1, [sp, #(8*16+0*8)]
    ldp x2, x3, [sp, #(8*16+2*8)]
    ldp x4, x5, [sp, #(8*16+4*8)]
    ldp x6, x7, [sp, #(8*16+6*8)]
    ldr x8,     [sp, #(8*16+8*8)]

    mov sp, fp
    ldp fp, lr, [sp], #16
    AuthenticateLR

.endmacro

MethodTableLookup中經(jīng)過一系列準(zhǔn)備工作,將會跳轉(zhuǎn)到_lookUpImpOrForward方法,全局查找_lookUpImpOrForward方法,但未找到,那么我們猜想一下是否_lookUpImpOrForward方法就不在匯編中,而是在C/C++中?

驗證

創(chuàng)建繼承自NSObject的類LGPerson,并添加方法sayHellomain.m文件中創(chuàng)建實例并調(diào)用方法:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        LGPerson *person = [LGPerson alloc];
1        [person sayHello];
    }
    return 0;
}

在調(diào)用sayHello方法處打下斷點,打開Debug--->Debug Workflow --->Always show Disassembly,程序來到底層匯編中:

方法調(diào)用匯編查看

在圖中16行objc_msgSend方法處打下斷點,繼續(xù)下一步,程序停住,按下control + step into ,進(jìn)入到objc_msgSend 匯編方法中:
objc_msgSend方法匯編

發(fā)現(xiàn)此時objc_msgSend方法最后也跳轉(zhuǎn)到了_objc_msgSend_uncached 中,對我們上面的匯編代碼在cache中沒找到跳轉(zhuǎn)到_objc_msgSend_uncached方法一樣。
接著打下斷點,繼續(xù)讓程序進(jìn)入_objc_msgSend_uncached 方法匯編中:
_objc_msgSend_uncached匯編代碼

我們看到代碼最終會跳到方法lookUpImpOrForward中,在C++文件 objc-runtime-new.mm:6099 處。

慢速查找C/C++分析

全局搜索lookUpImpOrForward ,找到objc-runtime-new.mm 文件中對應(yīng)的C代碼

IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)
{
    const IMP forward_imp = (IMP)_objc_msgForward_impcache;
    IMP imp = nil;
    Class curClass;

    runtimeLock.assertUnlocked();

    // Optimistic cache lookup
    // 考慮多線程過程影響,如果別的線程緩存了,那么直接利用cache_getImp匯編方法快速查找,CacheLookup GETIMP, _cache_getImp
    // 找到了就直接返回imp, 沒找到繼續(xù)步驟
    if (fastpath(behavior & LOOKUP_CACHE)) {
        imp = cache_getImp(cls, sel);
        if (imp) goto done_nolock;
    }
    runtimeLock.lock();
   //是否是已經(jīng)存在的類,是否是已經(jīng)加載到內(nèi)存中的類,是的話才能繼續(xù)下面查找工作
    checkIsKnownClass(cls);
   // 類是否實現(xiàn)了,配置類的繼承鏈及元類鏈,為后續(xù)查找工作做好了準(zhǔn)備(雙向鏈表的結(jié)構(gòu))
    if (slowpath(!cls->isRealized())) {
        cls = realizeClassMaybeSwiftAndLeaveLocked(cls, runtimeLock);
    }
   // 是否類已經(jīng)初始化過了,進(jìn)行初始化
    if (slowpath((behavior & LOOKUP_INITIALIZE) && !cls->isInitialized())) {
        cls = initializeAndLeaveLocked(cls, inst, runtimeLock);
    }
   
    runtimeLock.assertLocked();
    curClass = cls;
   // 無限循環(huán)方法 當(dāng)imp = forward_imp時 break 退出循環(huán)
    for (unsigned attempts = unreasonableClassCount();;) {
        // curClass method list.
        // 最終使用二分法 查找當(dāng)前 curClass 類中是否有對應(yīng)的imp
        Method meth = getMethodNoSuper_nolock(curClass, sel);
        if (meth) {
            imp = meth->imp;
            goto done;
        }
        // 當(dāng)前類方法列表沒找到,將當(dāng)前類變?yōu)楫?dāng)前類的父類,如果父類為nil,則imp賦值forward_imp退出此for循環(huán)
        if (slowpath((curClass = curClass->superclass) == nil)) {
            // No implementation found, and method resolver didn't help.
            // Use forwarding.
            imp = forward_imp;
            break;
        }

        // Halt if there is a cycle in the superclass chain.
        if (slowpath(--attempts == 0)) {
            _objc_fatal("Memory corruption in class list.");
        }

        // Superclass cache.
        // 優(yōu)先通過cache_getImp 匯編查找父類緩存,如果找到直接下面的go done
        // 如果沒有找到,那么下一個循環(huán),找父類的方法列表,然后父類的父類的緩存,方法列表,直到最后父類為nil 退出循環(huán)。
        imp = cache_getImp(curClass, sel); 
        if (slowpath(imp == forward_imp)) {
            // Found a forward:: entry in a superclass.
            // Stop searching, but don't cache yet; call method
            // resolver for this class first.
            break;
        }
        if (fastpath(imp)) {
            // Found the method in a superclass. Cache it in this class.
            goto done;
        }
    }
    // No implementation found. Try method resolver once.
    
    // 到這里 說明沒找到方法,最后 給一次方法解析機會
    if (slowpath(behavior & LOOKUP_RESOLVER)) {
    //動態(tài)方法決議的控制條件,表示流程只走一次
        behavior ^= LOOKUP_RESOLVER;
        return resolveMethod_locked(inst, sel, cls, behavior);
    }

 done:
    // 將找到的sel/imp信息緩存進(jìn)cache
    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;
}

其中,getMethodNoSuper_nolock 查找當(dāng)前類中是否有sel所對應(yīng)的Methodmethod_t結(jié)構(gòu)體),
getMethodNoSuper_nolock ---> search_method_list_inline ---> findMethodInSortedMethodList

ALWAYS_INLINE static method_t *
findMethodInSortedMethodList(SEL key, const method_list_t *list)
{
    ASSERT(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;

    /** 舉例講解二分法 方(法列表從前往后 ,從大到小的順序排列)
    // 0x01 0x02 0x03 0x04 0x05 0x05 0x06 0x07 0x08
    
    // keyValue = 0x02
    // base  = 0x01
    // probe = 0x05 --> 0x03 --> 0x02
    // 1 count 8 >> 1 = 4
    // 2 count 4 >> 1 = 2
    // 3 count 2 >> 1 = 1  此時probe = 0x02 即找到了
    
    // keyValue = 0x07
    // base  = 0x01 --> 0x06
    // probe = 0x05 --> 0x07
    // 1 count 8 >> 1 = 4
    // 2 count 7 >> 1 = 3 此時probe = 0x07 即找到了
     */

    
    for (count = list->count; count != 0; count >>= 1) {
        probe = base + (count >> 1);
        
        uintptr_t probeValue = (uintptr_t)probe->name;
        
        if (keyValue == probeValue) {
            // `probe` is a match.
            // Rewind looking for the *first* occurrence of this value.
            // This is required for correct category overrides.
            // 此處還要考慮前面有沒有相同方法,因為考慮分類 ,分類的方法在最前面
            while (probe > first && keyValue == (uintptr_t)probe[-1].name) {
                probe--;
            }
            return (method_t *)probe;
        }
        
        if (keyValue > probeValue) {
            base = probe + 1;
            count--;
        }
    }
    
    return nil;
}

cache_getImp 方法解析

objc_msg_arm64.s 文件中cache_getimp 匯編代碼如下:

STATIC_ENTRY _cache_getImp

    GetClassFromIsa_p16 p0
        // 所帶的$0 與 objc_msgSend (NORMAL) 不一樣
    CacheLookup GETIMP, _cache_getImp

LGetImpMiss:
    mov p0, #0
    ret

    END_ENTRY _cache_getImp

發(fā)現(xiàn)匯編走的方法也是CacheLookup 方法 ,快速方法查找父類的緩存里是否有此方法的緩存。

總結(jié)

  • 所有實例方法的實現(xiàn)查找路徑為: --> 父類 --> NSObject --> nil
  • 所有類方法的實現(xiàn)查找路徑為:元類--> 根元類 --> NSObject --> nil
  • 如果在上方兩條方法尋找鏈中都未找到,則會嘗試動態(tài)方法決議,最后給一次機會。
  • 如果動態(tài)方法決議失敗,那么會觸發(fā)消息轉(zhuǎn)發(fā),尋找實現(xiàn)了此方法的別的類。
?著作權(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ù)。

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