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

我们这里发现了一个_objc_msgSend_uncached方法,我们在_objc_msgSend_uncached这行断点,然后ctrol+step into。

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

在这里我们找到了lookUpImpOrForward而且根据后面的objc-runtime-new.mm,我们可以知道这是
objc的源码中的内容,我们可以去开源的objc源码中去查看。在objc源码objc-runtime-new.mm的6095行我们找到了这个方法的源码实现。

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

源码流程分析

我们找到了lookUpImpOrForward的源码实现,接下来我们就一步一步的分析具体的流程。
第一步快速查找,先走一遍从缓存查找,防止多线程调用时已经缓存了。

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

第二步 ,判断是否是一个已知的类,如果是已知类即已经加载的类。如果不是已知类,就报错。

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为什么说是在第一个消息发送之前被调用的原因。

    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循环按照类继承链或者元类继承链的顺序查找。

 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);//内部实现使用调用了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 = 消息转发,并终止递归,进入第五步
      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.");
        }
  • 父类缓存中查找方法

    • 如果没有找到,break,继续循环。

    • 如果找到,则直接返回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;
        }

第五步,动态方法决议。

    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的实现源码。打印出崩溃信息。

// 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);
}

你可能感兴趣的:(objc_msgSend慢速查找流程)