objc_msgSend慢速查找流程

前言

我們知道,objective-c中我們調用方法之后,底層會對方法進行緩存,讓后面再調用更加快捷。今天我們主要研究是在方法沒有緩存時,底層的查找流程。

調試分析

我們首先通過斷點+匯編的方式來跟蹤代碼的運行流程。我們在方法調用時進行了斷點,然后xcode->Debug->Debug Workflow->Always Show Disassembl選中,當我們運行到斷點時候我們會看到匯編的信息。

0x100000cf3 <+51>: movq   0x14d6(%rip), %rsi        ; "sayHello"
0x100000cfa <+58>: callq  *0x300(%rip)              ; (void *)0x00000001002c1500: objc_msgSend

我們看到方法進入了objc_msgSend。我們在這一行再打一個斷點,然后ctrol+step into。

 0x1002c158a <+138>: leaq   0x72dcf(%rip), %r10       ; objc_debug_taggedpointer_classes
0x1002c1591 <+145>: movq   (%r10,%r11,8), %r10
0x1002c1595 <+149>: leaq   0x72bcc(%rip), %r11       ; (void *)0x0000000100334118: __NSUnrecognizedTaggedPointer
0x1002c159c <+156>: cmpq   %r10, %r11
0x1002c159f <+159>: jne    0x1002c151a               ; <+26>
0x1002c15a5 <+165>: movl   %edi, %r11d
0x1002c15a8 <+168>: shrl   $0x4, %r11d
0x1002c15ac <+172>: andl   $0xff, %r11d
0x1002c15b3 <+179>: leaq   0x72e26(%rip), %r10       ; objc_debug_taggedpointer_ext_classes  
0x1002c15ba <+186>: movq   (%r10,%r11,8), %r10   
0x1002c15be <+190>: jmp    0x1002c151a               ; <+26>
0x1002c15c3 <+195>: jmp    0x1002c2050               ; _objc_msgSend_uncached

我們這里發(fā)現(xiàn)了一個_objc_msgSend_uncached方法,我們在_objc_msgSend_uncached這行斷點,然后ctrol+step into。

0x1002c2094 <+68>:  callq  0x1002e6840               ; lookUpImpOrForward at objc-runtime-new.mm:6095

在這里我們找到了lookUpImpOrForward而且根據(jù)后面的objc-runtime-new.mm,我們可以知道這是
objc的源碼中的內容,我們可以去開源的objc源碼中去查看。在objc源碼objc-runtime-new.mm的6095行我們找到了這個方法的源碼實現(xiàn)。

IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior) {
...
}

源碼流程分析

我們找到了lookUpImpOrForward的源碼實現(xiàn),接下來我們就一步一步的分析具體的流程。
第一步快速查找,先走一遍從緩存查找,防止多線程調用時已經(jīng)緩存了。

// Optimistic cache lookup
    if (fastpath(behavior & LOOKUP_CACHE)) {
        imp = cache_getImp(cls, sel);
        if (imp) goto done_nolock;
    }

第二步 ,判斷是否是一個已知的類,如果是已知類即已經(jīng)加載的類。如果不是已知類,就報錯。

checkIsKnownClass(cls);//判斷類是否加載了

ALWAYS_INLINE
static void
checkIsKnownClass(Class cls)
{
    if (slowpath(!isKnownClass(cls))) {
        _objc_fatal("Attempt to use unknown class %p.", cls);
    }
}

第三步,這里會看initialize是否有緩存,如果沒有會調用initializeAndLeaveLocked,先調用一遍initialize方法加入緩存中。這也是initialize為什么說是在第一個消息發(fā)送之前被調用的原因。

    if (slowpath((behavior & LOOKUP_INITIALIZE) && !cls->isInitialized())) {
        cls = initializeAndLeaveLocked(cls, inst, runtimeLock);
        // runtimeLock may have been dropped but is now locked again
        // If sel == initialize, class_initialize will send +initialize and 
        // then the messenger will send +initialize again after this 
        // procedure finishes. Of course, if this is not being called 
        // from the messenger then it won't happen. 2778172
    }

第四步,for循環(huán)按照類繼承鏈或者元類繼承鏈的順序查找。

 for (unsigned attempts = unreasonableClassCount();;) {
        // curClass method list.
        Method meth = getMethodNoSuper_nolock(curClass, sel);
        if (meth) {
            imp = meth->imp;
            goto done;
        }

        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.
        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;
        }
    }
  • 當前cls的方法列表中使用了二分查找算法查找,如果找到,則進入cache緩存流程,并返回imp,如果沒有找到,則返回nil.
    getMethodNoSuper_nolock->search_method_list_inline->findMethodInSortedMethodList
Method meth = getMethodNoSuper_nolock(curClass, sel);//內部實現(xiàn)使用調用了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;
    for (count = list->count; count != 0; count >>= 1) {
        probe = base + (count >> 1);//向右偏移一位,即等價于/2,二分查找
        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;
}
  • 將cls賦值為父類,如果父類等于nil,則imp = 消息轉發(fā),并終止遞歸,進入第五步
      if (slowpath((curClass = curClass->superclass) == nil)) {
            // No implementation found, and method resolver didn't help.
            // Use forwarding.
            imp = forward_imp;
            break;
        }
  • 如果父類鏈中存在循環(huán)則報錯,中止。
        // Halt if there is a cycle in the superclass chain.
        if (slowpath(--attempts == 0)) {
            _objc_fatal("Memory corruption in class list.");
        }
  • 父類緩存中查找方法

    • 如果沒有找到,break,繼續(xù)循環(huán)。

    • 如果找到,則直接返回imp,寫入cache緩存。

   // Superclass cache.
        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;
        }

第五步,動態(tài)方法決議。

    if (slowpath(behavior & LOOKUP_RESOLVER)) {
        behavior ^= LOOKUP_RESOLVER;
        return resolveMethod_locked(inst, sel, cls, behavior);
    }

最后如果還是沒有找到,則會調用__objc_msgForward_impcache->__objc_msgForward->__objc_forward_handler,在源碼中查找到void _objc_forward_handler = (void)objc_defaultForwardHandler,objc_defaultForwardHandler的實現(xiàn)源碼。打印出崩潰信息。

// Default forward handler halts the process.
__attribute__((noreturn, cold)) void
objc_defaultForwardHandler(id self, SEL sel)
{
    _objc_fatal("%c[%s %s]: unrecognized selector sent to instance %p "
                "(no message forward handler is installed)", 
                class_isMetaClass(object_getClass(self)) ? '+' : '-', 
                object_getClassName(self), sel_getName(sel), self);
}
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

友情鏈接更多精彩內容