iOS底层原理--方法的慢速查找流程

623 阅读7分钟

前言

iOS底层原理--RunTime之objc_msgSend探究(快速查找)这篇文章中,我们已经分析了消息的快速查找流程,如果在快速查找流程中没有找到,就会进入到__objc_msgSend_uncached,也就是慢速查找流程。下面👇我们就进入到慢速查找流程就行分析。

源码分析

我们先进入源码工程,找到__objc_msgSend_uncached的汇编源码,发现去掉注释有用的代码就两行。 image.png 我们先来看下TailCallFunctionPointer x17方法,发现该方法只是跳转返回x17的值。 image.png 具体x17里存的是什么,我们还不知道,这就得看下第一个方法MethodTableLookup做了哪些操作。字面意思方发表查找,我们进入源码看下。 image.png 此处x16是class,这个大家需要记得。在源码中,我们发现_lookUpImpOrForward方法的返回值,会被给x17寄存器,说明该方法返回的极有可能就是imp。 我们查找_lookUpImpOrForward方法,发现未在源码中找到,那么可能是汇编调用了c/c++方法,去掉一个下划线继续查找看看。

IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)
{
    /*
     定义的消息转发
     behavior==3 = LOOKUP_INITIALIZE|LOOKUP_RESOLVER
     */
    const IMP forward_imp = (IMP)_objc_msgForward_impcache;
    IMP imp = nil;
    Class curClass;

    runtimeLock.assertUnlocked();

    /*
     判断类是否已经初始化,如果没有初始化的话
     behavior = LOOKUP_NOCACHE|LOOKUP_INITIALIZE|LOOKUP_RESOLVER
     */
    if (slowpath(!cls->isInitialized())) {
        behavior |= LOOKUP_NOCACHE;
    }
    //加锁,目的是保证读取的线程安全
    runtimeLock.lock();
    //是否是已知的类,也就是说该类是否已经加载过了
    checkIsKnownClass(cls);
    //判断类是否实现,如果没有,需要先实现,此时的目的是为了确定父类链,方法后续的循环
    cls = realizeAndInitializeIfNeeded_locked(inst, cls, behavior & LOOKUP_INITIALIZE);
    // runtimeLock may have been dropped but is now locked again
    runtimeLock.assertLocked();
    curClass = cls;
    //死循环
    for (unsigned attempts = unreasonableClassCount();;) {
        ////判断是否有共享缓存缓存优化
        if (curClass->cache.isConstantOptimizedCache(/* strict */true)) {
#if CONFIG_USE_PREOPT_CACHES
            /*
             如果是真机,再走一次快速查找流程,
             可能在走的这步的过程中,别的线程调用了该方法,严谨一些
             */
            imp = cache_getImp(curClass, sel);
            //如果缓存中找到imp,则跳转到done_unlock
            if (imp) goto done_unlock;
            curClass = curClass->cache.preoptFallbackClass();
#endif
        } else {
            // curClass method list.
            //采用二分法查找methodList
            Method meth = getMethodNoSuper_nolock(curClass, sel);
            //如果找到了,则获取imp跳到done.
            if (meth) {
                imp = meth->imp(false);
                goto done;
            }
            //直到当前类的父类为nil为止(也就是NSObject),将定义的消息转发赋值给imp,跳出循环
            if (slowpath((curClass = curClass->getSuperclass()) == 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.
        // 从父类cache中找(快速查找)
        imp = cache_getImp(curClass, sel);
        //如果父类返回的是forward_imp 停止查找,跳出循环
        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;
        }
        //如果父类找到了imp,则跳到done
        if (fastpath(imp)) {
            // Found the method in a superclass. Cache it in this class.
            goto done;
        }
    }

    // No implementation found. Try method resolver once.
    /*
     behavior = 3  LOOKUP_RESOLVER = 2
     3&2 = 0011 & 0010 = 0010 与操作全部为1才为1
     3^2 = 0011 ^ 0010 = 0001 = behavior
     所以第二次进来 1 & 2 = 0001 & 0010 = 0000 条件不成立也就进不来了
     */
    if (slowpath(behavior & LOOKUP_RESOLVER)) {
        behavior ^= LOOKUP_RESOLVER;
        //动态方法决议
        return resolveMethod_locked(inst, sel, cls, behavior);
    }

 done:
    // 3 & 8 = 0011 & 1000 = 0000 = 0
    if (fastpath((behavior & LOOKUP_NOCACHE) == 0)) {
#if CONFIG_USE_PREOPT_CACHES
        while (cls->cache.isConstantOptimizedCache(/* strict */true)) {
            cls = cls->cache.preoptFallbackClass();
        }
#endif
        //将查询到的sel和imp插入到缓存 注意:插入的是当前类的缓存
        log_and_fill_cache(cls, imp, sel, inst, curClass);
    }
 done_unlock:
    runtimeLock.unlock();
    
    if (slowpath((behavior & LOOKUP_NIL) && imp == forward_imp)) {
        return nil;
    }
    return imp;
}

源码中已经标明注释,下面我们进行一下文字总结。

慢速查找流程总结

  • 定义消息转发IMP
  • 判断类是否已经初始化
  • 类是否已加载
  • 类是否已实现,确定父类/元类链路,方便进行递归查找
  • 递归查找
    • 判断是否有共享缓存优化
      • 如果有,走快速查找流程,找到后,插入缓存返回IMP
      • 如果没有,进行二分法在当前类中methodList中找method,如果找到,则返回对应的IMP,如果没有找到,继续执行。直到找到NSObject,跳出循环。
    • 如果父类链中有循环,则抛异常停止
    • 在父类cache中快速查找IMP
    • 如果父类返回的是forward_imp 停止查找,跳出循环
    • 如果父类找到了imp,则将查询到的sel和imp插入到缓存 注意:插入的是当前类的缓存
    • 如果cls以及父类都没有查询到,此时系统会给你一次机会,判断是否执行过动态方法决议,如果没有则走动态方法决议。

各个方法详解

checkIsKnownClass()

这个方法主要是判断,当前类是否已经注册加载进来了,通过下面的源码,我们得出如果当前类未知, 直接返回错误信息"Attempt to use unknown class XXXX ...." image.png

realizeAndInitializeIfNeeded_locked(id inst, Class cls, bool initialize)

我们通过源码发现,主要实现方法是其中实现和初始化的类方法,下面我们分别看下这两个方法。 image.png

realizeClassMaybeSwiftAndLeaveLocked(Class cls, mutex_t& lock)

通过源码我们发现realizeClassMaybeSwiftMaybeRelockrealizeClassWithoutSwift这两个方法,其实就是递归实现下继承链和isa走位链相关的类 image.png

initializeAndLeaveLocked

通过源码我们发现initializeAndMaybeRelockinitializeNonMetaClass这两个方法,其实就是递归初始化继承链和isa走位链相关的类

对象方法: 先从当前类中找,没有接着去父类中找,还没有再接着去爷爷类中找->...->最后到根类中找。
类方法: 先从当前类的元类中找,没有去父元类中找->...->没有再去根元类找。

二分查找法

findMethodInSortedMethodList

前提: 非常重要的一个前提是,列表已经经过排序了,否则无法使用二分法。 image.png 举个🌰
假如list的count=8,我们要找sel在第2位。
第1次循环

  • count = 8, base = 0, probe = 0+4
  • 2!=4
  • 2<4 第2次循环
  • count = 8>>1 = 4; base = 0,probe = 0+2
  • 2==2,找到方法,返回 再举个🌰
    假如list的count=8,我们要找sel在第7位。
    第1次循环
  • count = 8, base = 0, probe = 0+4
  • 7!=4
  • 7>4, base = 4+1 = 5, count = 8-1 = 7 第2次循环
  • count = 7>>1 = 3, base = 5, probe = 5+1 = 6
  • 7!=6
  • 7>6, base = 6+1 = 7, count = 3-1 = 2 第3次循环
  • count = 2>>1 = 1, base = 7, probe = 7+0 =7
  • 7==7,找到方法,返回 大致画个流程图出来吧,是时候展现下才华了。 二分法查找流程图.png

cache_getImp

我们找到源码,发现也是汇编实现的。 image.png 我们看下此处的CacheLookUp是否和之前快速查找的一样。 image.png 通过源码,我们发现,如果cache没有命中,会直接返回nil,而不会进入慢速查找流程中。这是最大的区别。 父类快速查找流程图.png

_objc_msgForward_impcache

我们如果方法未实现,总是会抛出同一个错误 unrecognized selector sent to instance,这是为什么呢?我们分析下这个方法的源码,就可以得出结论了。 image.png 这里发现主要是__objc_forward_handler,我们找下这个方法的源码。 image.png 一切都清晰了。

全文总结

  • 实例方法(对象方法),先在当前类中进行快速查找。
    • 如果在cache中找不到,会进行慢速查找。
      • 慢速找到:方法存入cache,返回imp。
      • 找不到:依次类--父类--根类--nil,父类只会cache中找,找不到直接返回nil。
    • 找到,直接返回IMP。
  • 类方法,先在元类中快速查找,其慢速查找的父类链是:元类--根元类--根类--nil
    • 如果在cache中找不到,会进行慢速查找。
      • 找到:方法存入cache,返回imp。
      • 找不到:依次元类--根元类--根类--nil。
    • 找到,直接返回IMP。
  • 如果快速查找、慢速查找都找不大方法的实现(IMP),则尝试动态方法决议
  • 如果动态方法决议依然找不到,则会进行消息转发。 最后两步预知后事如何,且看下回分解。

慢速查找流程图

慢速查找流程图.png