[iOS底层]-objc_msgSend慢速查找流程

2020/10/7 posted in  iOS底层 总阅读量

慢速查找汇编部分

[iOS底层]-objc_msgSend快速查找原理中我们分析了缓存命中的情况,则会返回相应的imp,不过CheckMissJumpMiss的情况下要如何进行处理我们还没有进行分析。根据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查询方法列表的汇编源码如下,其中_lookUpImpOrForward是关键实现。

.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

    // 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

我们在汇编文件里搜索,发现无法查找到_lookUpImpOrForward的实现源码。

如何找到_lookUpImpOrForward的实现源码

  • 我们在main函数里调用一个没有方法实现的函数[p sayHello];,并打一个断点。
  • 执行到断点后,开启Debug->Debug Workflow->Always Show Disassembly

  • objc_msgSend一行打上断点,跳过当前断点,执行到objc_msgSend这个断点这里,按Control + step into,进入到objc_msgSend中。

  • 进去后在_objc_msgSend_uncached的地方打上一个断点,过掉当前断点,按Control + step into,进入_objc_msgSend_uncached

  • 可以看到lookUpImpOrForward at objc-runtime-new.mm,lookUpImpOrForward是一个C/C++方法,在objc-runtime-new.mm文件的6095行。

C/C++调用汇编,查找方法时需要增加一个_
汇编调用C/C++,全局查找方法时需要减少一个_

lookUpImpOrForward分析

  • lookUpImpOrForward源码如下:
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();

    // 在进行慢查找之前再次进行一次快查,防止多线程的时候,缓存有了。
    if (fastpath(behavior & LOOKUP_CACHE)) {
        imp = cache_getImp(cls, sel);
        if (imp) goto done_nolock;
    }
    
    // 加锁
    runtimeLock.lock();

    // 查询是否为已知类
    checkIsKnownClass(cls);

    // 类没有实现,尝试实现
    if (slowpath(!cls->isRealized())) {
        cls = realizeClassMaybeSwiftAndLeaveLocked(cls, runtimeLock);
        // runtimeLock may have been dropped but is now locked again
    }

    // 类如果没有初始化,进行初始化操作
    if (slowpath((behavior & LOOKUP_INITIALIZE) && !cls->isInitialized())) {
        cls = initializeAndLeaveLocked(cls, inst, runtimeLock);
    }

    runtimeLock.assertLocked();
    curClass = cls;

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

        if (slowpath((curClass = curClass->superclass) == nil)) {
            // 没找到,也没有父类,使用转发 
            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.");
        }

        // 获取父类的缓存
        imp = cache_getImp(curClass, sel);
        if (slowpath(imp == forward_imp)) {
            // 父类中找到的是forward_imp,则终止查找
            break;
        }
        if (fastpath(imp)) {
            // 找到imp,则跳转到 done,进行方法缓存
            goto done;
        }
    }

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

 done:
    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;
}

lookUpImpOrForward流程图如下: