iOS开发 — 方法查找流程(慢速)&动态方法解析

1,391 阅读6分钟

在上篇文章中我们说到objc_msgSend函数在底层汇编代码中查找缓存没有命中就会来到bl __class_lookupMethodAndLoadCache3,然后进入lookUpImpOrForward函数,开启方法的慢速查找流程。这次我们就来具体分析一下这个流程。

方法的慢速查找流程

lookUpImpOrForward

我们先来看一下lookUpImpOrForward的源码:

IMP lookUpImpOrForward(Class cls, SEL sel, id inst, 
                       bool initialize, bool cache, bool resolver)
{
    IMP imp = nil;
    bool triedResolver = NO;

    runtimeLock.assertUnlocked();

    // Optimistic cache lookup
    // 判断是否需要从缓存里面查找
    // 由于我们是没有在缓存中找到才来到这里,故为false
    if (cache) {
        imp = cache_getImp(cls, sel);
        if (imp) return imp;
    }

    // runtimeLock is held during isRealized and isInitialized checking
    // to prevent races against concurrent realization.

    // runtimeLock is held during method search to make
    // method-lookup + cache-fill atomic with respect to method addition.
    // Otherwise, a category could be added but ignored indefinitely because
    // the cache was re-filled with the old value after the cache flush on
    // behalf of the category.
    
    // 加锁
    runtimeLock.lock();
    // 判断是否为已经被编译进来的类
    checkIsKnownClass(cls);
    
    /**
    *   为查找方法作准备,判断类有没有加载
    *   如果没有加载,那就加载一下类,并且会递归准备好父类和元类
    */
    if (!cls->isRealized()) {
        realizeClass(cls);
    }

    /**
    *   如果sel == initialize,_class_initialize将会调用+initialize
    *   确保对象已被初始化
    */
    if (initialize  &&  !cls->isInitialized()) {
        runtimeLock.unlock();
        _class_initialize (_class_getNonMetaClass(cls, inst));
        runtimeLock.lock();
        // 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
    }

    
 retry:    
    runtimeLock.assertLocked();

    // Try this class’s cache.
    // 查找本类里的缓存
    imp = cache_getImp(cls, sel);
    if (imp) goto done;

    // Try this class’s method lists.
    /** 
    *   从本类的方法列表里循环查找方法
    *   这里使用了二分查找法
    *   如果找到了就会先填充进缓存再跳转到done
    */
    {
        Method meth = getMethodNoSuper_nolock(cls, sel);
        if (meth) {
            log_and_fill_cache(cls, meth->imp, sel, inst, cls);
            imp = meth->imp;
            goto done;
        }
    }

    // Try superclass caches and method lists.
    /** 
    *   查找父类的方法缓存和方法列表
    *   循环遍历父类,先查找方法缓存,找到后填充进缓存,然后直接跳转到done
    *   如果缓存里没找到,则继续在父类的方法列表里查找,找到后填充进缓存,直接跳转到done
    *   如果一直没找到,则当superclass为nil时,遍历结束
    */
    {
        unsigned attempts = unreasonableClassCount();
        for (Class curClass = cls->superclass;
             curClass != nil;
             curClass = curClass->superclass)
        {
            // Halt if there is a cycle in the superclass chain.
            if (--attempts == 0) {
                _objc_fatal("Memory corruption in class list.");
            }
            
            // Superclass cache.
            imp = cache_getImp(curClass, sel);
            if (imp) {
                if (imp != (IMP)_objc_msgForward_impcache) {
                    // Found the method in a superclass. Cache it in this class.
                    log_and_fill_cache(cls, imp, sel, inst, curClass);
                    goto done;
                }
                else {
                    // Found a forward:: entry in a superclass.
                    // Stop searching, but don’t cache yet; call method 
                    // resolver for this class first.
                    break;
                }
            }
            
            // Superclass method list.
            Method meth = getMethodNoSuper_nolock(curClass, sel);
            if (meth) {
                log_and_fill_cache(cls, meth->imp, sel, inst, curClass);
                imp = meth->imp;
                goto done;
            }
        }
    }

    // No implementation found. Try method resolver once.
    /**
    *   动态方法解析
    *   当本类和父类中都没有查找到方法时,Runtime给了我们一个机会进行动态方法解析
    *   由于函数内设置了triedResolver = YES;所以这里只来一次
    */
    if (resolver  &&  !triedResolver) {
        runtimeLock.unlock();
        _class_resolveMethod(cls, sel, inst);
        runtimeLock.lock();
        // Don’t cache the result; we don’t hold the lock so it may have 
        // changed already. Re-do the search from scratch instead.
        triedResolver = YES;
        goto retry;
    }

    // No implementation found, and method resolver didn’t help. 
    // Use forwarding.

    imp = (IMP)_objc_msgForward_impcache;
    cache_fill(cls, sel, imp, inst);

 done:
    runtimeLock.unlock();

    return imp;
}

源码的功能都写在注释中,如果在本类和父类中都没有查找到,就会来到动态方法解析。下面我们分析一下动态方法解析。

动态方法解析

_class_resolveMethod

先来看一下源码:

/***********************************************************************
* _class_resolveMethod
* Call +resolveClassMethod or +resolveInstanceMethod.
* Returns nothing; any result would be potentially out-of-date already.
* Does not check if the method already exists.
**********************************************************************/
void _class_resolveMethod(Class cls, SEL sel, id inst)
{
    // 判断当前类是否为元类
    if (! cls->isMetaClass()) {
        // try [cls resolveInstanceMethod:sel]

        _class_resolveInstanceMethod(cls, sel, inst);
    } 
    else {
        // try [nonMetaClass resolveClassMethod:sel]
        // and [cls resolveInstanceMethod:sel]
        _class_resolveClassMethod(cls, sel, inst);
        if (!lookUpImpOrNil(cls, sel, inst, 
                            NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) 
        {
            _class_resolveInstanceMethod(cls, sel, inst);
        }
    }
}

由源码可知这个函数的作用是判断当前类是否为元类:

  • 如果本类不是元类,说明方法为实例方法,调用_class_resolveInstanceMethod
  • 如果本类是元类,说明方法为类方法,调用_class_resolveClassMethod,然后再调用lookUpImpOrNil,如果没找到,再调用_class_resolveInstanceMethod

_class_resolveInstanceMethod

/***********************************************************************
* _class_resolveInstanceMethod
* Call +resolveInstanceMethod, looking for a method to be added to class cls.
* cls may be a metaclass or a non-meta class.
* Does not check if the method already exists.
**********************************************************************/
static void _class_resolveInstanceMethod(Class cls, SEL sel, id inst)
{
    if (! lookUpImpOrNil(cls->ISA(), SEL_resolveInstanceMethod, cls, 
                         NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) 
    {
        // Resolver not implemented.
        return;
    }

    BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
    bool resolved = msg(cls, SEL_resolveInstanceMethod, 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(cls, sel, inst, 
                             NO/*initialize*/, YES/*cache*/, NO/*resolver*/);

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

源码分析:

  • 判断当前类是否实现了SEL_resolveInstanceMethod方法。(这个方法在NSObject中已经实现,默认返回NO)
  • 向当前类发送SEL_resolveInstanceMethod消息,即调用这个方法。
  • 调用完成后,再次查看有没有sel对应的imp

_class_resolveClassMethod

/***********************************************************************
* _class_resolveClassMethod
* Call +resolveClassMethod, looking for a method to be added to class cls.
* cls should be a metaclass.
* Does not check if the method already exists.
**********************************************************************/
static void _class_resolveClassMethod(Class cls, SEL sel, id inst)
{
    assert(cls->isMetaClass());

    if (! lookUpImpOrNil(cls, SEL_resolveClassMethod, inst, 
                         NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) 
    {
        // Resolver not implemented.
        return;
    }

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

    // Cache the result (good or bad) so the resolver doesn’t fire next time.
    // +resolveClassMethod adds to self->ISA() a.k.a. cls
    IMP imp = lookUpImpOrNil(cls, sel, inst, 
                             NO/*initialize*/, YES/*cache*/, NO/*resolver*/);

    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 resolveClassMethod:%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));
        }
    }
}

其实这个方法和上个方法流程基本是一样的,不同的是发送消息的对象变成了元类,发送的消息变为SEL_resolveClassMethod。上述方法执行完毕后,程序会回归到lookUpImpOrForward中跳转到retry,再走一遍方法查找流程。

如果在方法查找流程中没有找到对应方法,并且动态方法解析也没有处理的话,就会进入最后一步,即消息转发流程,这也是苹果为了补救崩溃前的最后一步。笔者将在下一篇章中再做研究。

总结

我们来整体回顾一下方法的查找流程:

  • 首先OC方法会被编译成objc_msgSend等函数,方法的本质是发送消息。
  • 调用方法会先进入快速查找流程,是由汇编编写的,去查找缓存中是否有对应的函数实现,找到则返回,找不到则进入慢速查找流程。
  • 慢速查找流程会先依次查找本类的方法缓存和方法列表、父类的方法缓存和方法列表,找到则先存一份进缓存再返回,如果都找不到则进入方法动态解析。
  • 动态方法解析会根据当前是类还是元类来调用不同的方法,如果解析成功则返回,解析不成功则进入消息转发流程。