首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >方法的查找流程——慢速查找

方法的查找流程——慢速查找

作者头像
拉维
发布2021-03-10 14:17:14
发布2021-03-10 14:17:14
6670
举报
文章被收录于专栏:iOS小生活iOS小生活

我们看这样一个例子:

在NSObject的分类(NSObject+LG)里面定义并实现了sayMaster方法:

代码语言:javascript
复制
@interface NSObject (LG)
- (void)sayMaster;
@end

@implementation NSObject (LG)
- (void)sayMaster{
    NSLog(@"%s",__func__);
}
@end

Norman是继承自NSObject的一个类,它里面并没有定义任何方法:

代码语言:javascript
复制
@interface Norman : NSObject
@end

@implementation Norman
@end

现在我们在外界给Norman类调用sayMaster方法:

代码语言:javascript
复制
[Norman performSelector:@selector(sayMaster)];

此时会不会因为找不到对应的方法实现而报崩溃?

答案是不会。

也许你会有疑问,不是实例方法吗?为啥通过类对象也可以调用?且听我慢慢道来。

首先,先到Norman类对象的类(即Norman元类)中去查找有没有对应的方法,没有的话就去Norman元类的父类(即根元类)中去找,再没有的话就去根元类的父类(即NSObject类对象)中去找,此时找到了,所以不会崩溃

想必大家已经对方法的查找流程有过基本的了解了,所以这个例子大家应该都能理解,接下来我们就从源码层面来分析方法的慢速查找流程。

方法的慢速查找流程分析

在上篇文章方法的查找流程——快速查找中,我们知道,在缓存中没有查找到对应的方法之后,最终会走到_class_lookupMethodAndLoadCache3函数,今天我们就从该函数开始入手研究。

首先看其声明:

代码语言:javascript
复制
/* message dispatcher */
extern IMP _class_lookupMethodAndLoadCache3(id, SEL, Class);

这里定义成extern的原因就是为了能够在外界使用该函数。

然后看其实现:

代码语言:javascript
复制
IMP _class_lookupMethodAndLoadCache3(id obj, SEL sel, Class cls)
{
    return lookUpImpOrForward(cls, sel, obj, 
                              YES/*initialize*/, NO/*cache*/, YES/*resolver*/);
}

这里就是简单地调用了lookUpImpOrForward函数。这里需要说明以下几点:

  1. 这里的参数obj是当前方法的调用者,cls参数是方法开始查找的起始类。
  2. cache参数传的是NO,因为此时_class_lookupMethodAndLoadCache3函数是在缓存查找没有命中之后才走到这里调用的,因此能够走到这里,那么cache就必然传NO

接下来查看lookUpImpOrForward函数的源码:

代码语言:javascript
复制
/***********************************************************************
* lookUpImpOrForward.
* The standard IMP lookup. 
* initialize==NO tries to avoid +initialize (but sometimes fails)
* cache==NO skips optimistic unlocked lookup (but uses cache elsewhere)
* Most callers should use initialize==YES and cache==YES.
* inst is an instance of cls or a subclass thereof, or nil if none is known. 
*   If cls is an un-initialized metaclass then a non-nil inst is faster.
* May return _objc_msgForward_impcache. IMPs destined for external use 
*   must be converted to _objc_msgForward or _objc_msgForward_stret.
*   If you don't want forwarding at all, use lookUpImpOrNil() instead.
**********************************************************************/
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
    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);
    }

    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.
    {
        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.
    {
        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.

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

第53行及之前都是一些准备条件,我们不需要看。我们从第54行开始研究。

第57~59行是去当前类的缓存中去查找,需要说明的是,这里不用走汇编。因为第53行及之前的准备条件已经将缓存给准备好了,这里可以直接获取。

第61~70行是到当前类的方法列表中去查找。这里有以下几点需要说明:

  1. 在methodList中查找某个method的算法是二分查找算法
  2. 在methodList中找到对应函数实现之后,会通过log_and_fill_cache来缓存该函数实现
  3. 我们点击log_and_fill_cache查看其实现:
代码语言:javascript
复制
static void
log_and_fill_cache(Class cls, IMP imp, SEL sel, id receiver, Class implementer)
{
#if SUPPORT_MESSAGE_LOGGING
    if (objcMsgLogEnabled) {
        bool cacheIt = logMessageSend(implementer->isMetaClass(), 
                                      cls->nameForLogging(),
                                      implementer->nameForLogging(), 
                                      sel);
        if (!cacheIt) return;
    }
#endif
    cache_fill (cls, sel, imp, receiver);
}

然后点击cache_fill查看其实现:

代码语言:javascript
复制
void cache_fill(Class cls, SEL sel, IMP imp, id receiver)
{
#if !DEBUG_TASK_THREADS
    mutex_locker_t lock(cacheUpdateLock);
    cache_fill_nolock(cls, sel, imp, receiver);
#else
    _collecting_in_critical();
    return;
#endif
}

在这里我们看到了一个熟悉的函数:cache_fill_nolock!!!cache_fill_nolock是谁呢?就是我前面在OC类的原理探究(二)——方法的缓存中提到的方法缓存的入口!!!之前我们知道在cache_fill_nolock里面进行扩容、缓存函数等,但是并不知道在哪里会调用cache_fill_nolock函数,现在我们知道cache_fill_nolock函数的调用的地方了,这就串起来了!

第72行~第108行,是在当前类中没找到对应的方法实现后,到父类当中去查找。跟在当前类中查找的流程一样,也是先到父类缓存中去查找,父类缓存中没找到的话,那就到父类的方法列表中通过二分查找算法去查找

这里需要说明的是:到父类缓存中去查找不需要走汇编了,因为在第53行之前的准备中就已经将对应的缓存给准备好了,可以直接去缓存中获取。

当找遍当前类及其所有的父类之后,还是没有找到对应的方法实现,那么就会进入下面的消息转发流程。

从第110行往下,都是消息转发流程的相关内容。

我在下面三篇文章中都对消息转发流程做了相关讲述,感兴趣的话可以看一下:

  • Runtime——消息转发流程
  • Effective Objective-C 2.0——理解消息转发机制
  • Runtime再理解

以上。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-02-14,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 iOS小生活 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档