iOS底层原理:消息转发之慢速查找

在【iOS底层原理:objc_msgSend之缓存查找】中我们分析到,如果缓存没有命中的时候,会默认找到_lookUpImpOrForward方法。

其实如果看过源码,我们都知道应该lookUpImpOrForward就是我们慢速查找方法的入口方法了。那么我们新手如何知道是这个方法呢?

1、查找lookUpImpOrForward方法

1.1、打断点到我们调用的任意一个方法

断点到方法

1.2、在Xcode菜单栏中选择Debug -> Debug Workflow -> Always Show Disassembly,然后断点到 objc_msgSend方法处

objc_msgSend

1.3、然后 【control + step into】 进入objc_msgSend方法

objc_msgSend方法

从快速查找的过程中,其实我们知道了如果缓存未命中,会走到_objc_msgSend_uncached方法中,所以,我们直接断点到该方法处

1.4、【control + step into】 进入_objc_msgSend_uncached方法

_objc_msgSend_uncached

在改方法的汇编中,我们可以发现了我们需要找的方法lookUpImpOrForward

其实,在oc中,当由汇编到c++的时候,会减少一个_,当c++到c方法的时候,会再次减少一个_

2、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();

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

    if (slowpath((behavior & LOOKUP_INITIALIZE) && !cls->isInitialized())) {
        cls = initializeAndLeaveLocked(cls, inst, runtimeLock);
    }

    runtimeLock.assertLocked();
    curClass = cls;

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

        if (slowpath((curClass = curClass->superclass) == nil)) {
            imp = forward_imp;
            break;
        }

        if (slowpath(--attempts == 0)) {
            _objc_fatal("Memory corruption in class list.");
        }

        imp = cache_getImp(curClass, sel);
        if (slowpath(imp == forward_imp)) {
            break;
        }
        if (fastpath(imp)) {
            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;
}

2.1、cache_getImp

首先会从缓存中进行查找,Why?

因为我们在整个运行过程中,是多线程的,可能在正好在调用的时候,该方法已经在其它线程中已经加入缓存了。

2.2、checkIsKnownClass

主要是判断当前类是否已经加载到内存中了。

2.3、realizeClassMaybeSwiftAndLeaveLocked

主要是bits的赋值,将ro赋值给rw,并且内部通过realizeClassWithoutSwift将整个继承链都确定下来,方便我们后续进行慢速查找的过程中,可以通过继承链来查找。

2.3.1、realizeClassWithoutSwift 分析

    auto ro = (const class_ro_t *)cls->data();
    auto isMeta = ro->flags & RO_META;
    if (ro->flags & RO_FUTURE) {
        // This was a future class. rw data is already allocated.
        rw = cls->data();
        ro = cls->data()->ro();
        ASSERT(!isMeta);
        cls->changeInfo(RW_REALIZED|RW_REALIZING, RW_FUTURE);
    } else {
        // Normal class. Allocate writeable class data.
        rw = objc::zalloc();
        rw->set_ro(ro);
        rw->flags = RW_REALIZED|RW_REALIZING|isMeta;
        cls->setData(rw);
    }

上面的代码主要是进行了rw数据的赋值。

    cls->superclass = supercls;
    cls->initClassIsa(metacls);

    if (supercls  &&  !isMeta) reconcileInstanceVariables(cls, supercls, ro);

    cls->setInstanceSize(ro->instanceSize);

    if (ro->flags & RO_HAS_CXX_STRUCTORS) {
        cls->setHasCxxDtor();
        if (! (ro->flags & RO_HAS_CXX_DTOR_ONLY)) {
            cls->setHasCxxCtor();
        }
    }

    if ((ro->flags & RO_FORBIDS_ASSOCIATED_OBJECTS) ||
        (supercls && supercls->forbidsAssociatedObjects()))
    {
        rw->flags |= RW_FORBIDS_ASSOCIATED_OBJECTS;
    }

    if (supercls) {
        addSubclass(supercls, cls);
    } else {
        addRootClass(cls);
    }

    methodizeClass(cls, previously);

上面的代码主要是进行了cls结构的确定。

cls->superclass = supercls;addSubclass(supercls, cls);可以看出其实cls是一个双向链表结构。

methodizeClass(cls, previously);其实是对rw进行处理,将所有的方法属性协议 attachLists 到rw

2.4、initializeAndLeaveLocked

主要是初始化所有类,即递归调用initialize方法。

整个流程如下:
initializeAndLeaveLocked -> initializeAndMaybeRelock -> initializeNonMetaClass -> callInitialize -> ((void(*)(Class, SEL))objc_msgSend)(cls, @selector(initialize));

2.5、imp 分析

为什么这一步是分析imp呢?

其实整个的imp主要是在for循环中。这个for循环是一层死循环。

2.5.1、分析for死循环

  • 1、getMethodNoSuper_nolock

先从当前类进行查找。

static method_t *
getMethodNoSuper_nolock(Class cls, SEL sel)
{
    runtimeLock.assertLocked();

    ASSERT(cls->isRealized());

    auto const methods = cls->data()->methods();
    for (auto mlists = methods.beginLists(),
              end = methods.endLists();
         mlists != end;
         ++mlists)
    {
        method_t *m = search_method_list_inline(*mlists, sel);
        if (m) return m;
    }

    return nil;
}

从上面的代码中,我们可以看到其实是从clsdatamethods方法中进行二分查找

通过 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;
    
    for (count = list->count; count != 0; count >>= 1) {
        probe = base + (count >> 1);  // 右移1位,就是除2
        
        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;
}

probe = base + (count >> 1); 右移1位,就是除2;
while循环中其实查找同名方法,即查找添加的所有的分类,找到最后一个添加的分类的方法实现;

  • 2、当在当前类中没找到方法时,会首先调用curClass = curClass->superclass将当前类的父类赋值给当前类,即下一步回去父类进行查找。
  • 3、然后会调用imp = cache_getImp(curClass, sel);去查找父类的缓存
    • 当我们点进去的时候,发现实现如下:
extern IMP cache_getImp(Class cls, SEL sel);

到了此处我们又是找不到去哪里了,那么根据我们的经验,可以推测下,查找缓存应该还会是快速查找,即汇编实现,那么根据第一步的流程,我们全局搜索下_cache_getImp

_cache_getImp 汇编实现如下:

STATIC_ENTRY _cache_getImp

    GetClassFromIsa_p16 p0
    CacheLookup GETIMP, _cache_getImp

LGetImpMiss:
    mov p0, #0
    ret

    END_ENTRY _cache_getImp

GetClassFromIsa_p16 p0: p0也就是我们的curClassisa

然后CacheLookup其实就是我们以前博客中提到的快速查找的流程了,具体查找过程可以看【iOS底层原理:objc_msgSend之缓存查找】

其中唯一的差异性就是,传入的参数,_objc_msgSend缓存查找的传参为NORMAL_cache_getImp的方法调用GETIMP

// _objc_msgSend 方法
CacheLookup NORMAL, _objc_msgSend
// _cache_getImp 方法
CacheLookup GETIMP, _cache_getImp

当缓存中没找到的时候,会找到JumpMiss或者CheckMiss中;

.macro CheckMiss
    // miss if bucket->sel == 0
.if $0 == GETIMP
    cbz p9, LGetImpMiss
.elseif $0 == NORMAL
    cbz p9, __objc_msgSend_uncached
.elseif $0 == LOOKUP
    cbz p9, __objc_msgLookup_uncached
.else
.abort oops
.endif
.endmacro

.macro JumpMiss
.if $0 == GETIMP
    b   LGetImpMiss
.elseif $0 == NORMAL
    b   __objc_msgSend_uncached
.elseif $0 == LOOKUP
    b   __objc_msgLookup_uncached
.else
.abort oops
.endif
.endmacro

通过上面的代码可以知道最后是调用了LGetImpMiss

LGetImpMiss:
    mov p0, #0
    ret

    END_ENTRY _cache_getImp

LGetImpMiss方法中,其实就返回了p0,也就是当前的cls,即父类的cls

那然后会一直走1、2、3、的流程,直到跳出循环,也就是走到4

  • 4、如果当imp == forward_imp时,会结束当前循环,或者当(curClass = curClass->superclass) == nil时,也会将imp赋值为forward_imp跳出死循环

那么什么是forward_imp呢?

在源码的第一行中其实就赋值了

const IMP forward_imp = (IMP)_objc_msgForward_impcache;

那我们在全局搜索一下_objc_msgForward_impcache,发现有调用到了汇编中

STATIC_ENTRY __objc_msgForward_impcache

    // No stret specialization.
    b   __objc_msgForward

    END_ENTRY __objc_msgForward_impcache

    
    ENTRY __objc_msgForward

    adrp    x17, __objc_forward_handler@PAGE
    ldr p17, [x17, __objc_forward_handler@PAGEOFF]
    TailCallFunctionPointer x17
    
    END_ENTRY __objc_msgForward

在汇编中我们可以看到__objc_msgForward_impcache中其实就是调用了__objc_msgForward,而__objc_msgForward中其实主要是调用了__objc_forward_handler,那我们在全局搜索的时候,发现并没有找到对应的实现。

所以,根据我们第一步的流程,如果汇编找不到,就去找c++的方法,去掉一个_,即_objc_forward_handler

然后我们可以发现代码如下:

// 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);
}
void *_objc_forward_handler = (void*)objc_defaultForwardHandler;

这就是我们平时经常看到的报错信息了。

2.5.2、resolveMethod_locked

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

上面的判断主要是控制当前的resolveMethod_locked只会走一次
当上诉的死循环跳出来后,会走到当前方法resolveMethod_locked。这就是我们常见的动态方法决议

resolveMethod_locked 分析
static NEVER_INLINE IMP
resolveMethod_locked(id inst, SEL sel, Class cls, int behavior)
{
    runtimeLock.assertLocked();
    ASSERT(cls->isRealized());

    runtimeLock.unlock();

    if (! cls->isMetaClass()) {
        // try [cls resolveInstanceMethod:sel]
        resolveInstanceMethod(inst, sel, cls);
    } 
    else {
        // try [nonMetaClass resolveClassMethod:sel]
        // and [cls resolveInstanceMethod:sel]
        resolveClassMethod(inst, sel, cls);
        if (!lookUpImpOrNil(inst, sel, cls)) {
            resolveInstanceMethod(inst, sel, cls);
        }
    }

    // chances are that calling the resolver have populated the cache
    // so attempt using it
    return lookUpImpOrForward(inst, sel, cls, behavior | LOOKUP_CACHE);
}

在该方法中,我们先分析不是元类的情况,即会调用resolveInstanceMethod

resolveInstanceMethod源码实现如下:

static void resolveInstanceMethod(id inst, SEL sel, Class cls)
{
    runtimeLock.assertUnlocked();
    ASSERT(cls->isRealized());
    SEL resolve_sel = @selector(resolveInstanceMethod:);

    if (!lookUpImpOrNil(cls, resolve_sel, cls->ISA())) {
        // Resolver not implemented.
        return;
    }

    BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
    bool resolved = msg(cls, resolve_sel, sel);

    // Cache the result (good or bad) so the resolver doesn't fire next time.
    // +resolveInstanceMethod adds to self a.k.a. cls
    IMP imp = lookUpImpOrNil(inst, sel, cls);

    if (resolved  &&  PrintResolving) {
        if (imp) {
            _objc_inform("RESOLVE: method %c[%s %s] "
                         "dynamically resolved to %p", 
                         cls->isMetaClass() ? '+' : '-', 
                         cls->nameForLogging(), sel_getName(sel), imp);
        }
        else {
            // Method resolver didn't add anything?
            _objc_inform("RESOLVE: +[%s resolveInstanceMethod:%s] returned YES"
                         ", but no new implementation of %c[%s %s] was found",
                         cls->nameForLogging(), sel_getName(sel), 
                         cls->isMetaClass() ? '+' : '-', 
                         cls->nameForLogging(), sel_getName(sel));
        }
    }
}

static inline IMP
lookUpImpOrNil(id obj, SEL sel, Class cls, int behavior = 0)
{
    return lookUpImpOrForward(obj, sel, cls, behavior | LOOKUP_CACHE | LOOKUP_NIL);
}
  • 1、在resolveInstanceMethod中,首先调用lookUpImpOrNil判断是否实现了resolveInstanceMethod,也就是我们说的动态方法决议
  • 2、然后会调用objc_msgSend进行resolveInstanceMethod的消息转发,并将未找到的sel作为参数传出去
  • 3、最后再次调用lookUpImpOrNil进行查询原来的方法是否有实现,也就是第二步是否有将imp进行处理,重新走lookUpImpOrForward流程。

2.6、log_and_fill_cache

主要是将整个方法加入到缓存中,也就是方便我们后续更快的查找方法。
整个流程如下:
log_and_fill_cache -> cache_fill -> cache->insert

你可能感兴趣的:(iOS底层原理:消息转发之慢速查找)