论objc_msgSend消息机制之消息查找

858 阅读8分钟
一.探索前需知

1.对象方法存在类里,类方法会存储在元类里(元类是系统在编译时为我们自动创建的类)类在元类里是以对象方式存在的也就是类对象.

2. 在上篇文章中讨论了方法在类中查找首先通过快速查找机制会先从类里cache_t先去查找,如果cache命中就触发消息发送,如果缓存没有命中,就会通过 JumpMiss .来到    __objc_msgSend_uncached -> MethodTableLookup最后调用 bl __class_lookupMethodAndLoadCache3, 触发消息的慢速查找流程.(附:上篇博客地址:juejin.cn/post/684490…)

二.慢速查找流程之初步了解

平时工作中方法调用无外乎与这几种场景:(LGStudent继承自LGTeacher,LGTeacher继承于NSObject)

对象方法:

2.1 对象调用自己的方法

 LGStudent *student = [[LGStudent alloc] init];
 [student sayHello];

2.2 对象调用父类的方法

 [student sayNB];

2.3 对象调用父类的父类方法

[student sayMaster];

2.4 如果调用的方法并没有在本类、父类以及NSObject里实现就会报经典的错误

 [student performSelector:@selector(sayLove)];

[LGStudent saySomething]: unrecognized selector sent to instance 0x100583970

类方法:

2.5 本类有 - 返回自己

[LGStudent sayObjc];

2.6 本类没有 - 父类里有

[LGStudent sayHappay];

2.7 本类没有 - 父类没有 - 根类NSObject里有

[LGStudent sayEasy];

2.8 本类没有 - 父类没有 - 根类NSObject里也没有

[LGStudent performSelector:@selector(sayLove)];

reason: '+[LGStudent sayLove]: unrecognized selector sent to class 0x100002290'

这里是我们平时开发过程中调用方法几乎遇到的所有场景,那么这个方法调用以及查找到底是怎么样的呢?那么我们只有在源码里一探究竟呐.

三.慢速查找流程之进阶

在这里类方法和实例方法就放在一块来分析了:

首先对象方法 :

 LGStudent *student = [[LGStudent alloc] init];
 [student sayHello];

1.首先进入

其中cls是LGStudent,sel是sayHello

 我们看lookUpImpOrForward的实现:

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

我们来一步一步来分析.

    IMP imp = nil;
    bool triedResolver = NO;
    runtimeLock.assertUnlocked();
    // Optimistic cache lookup
    if (cache) {
        imp = cache_getImp(cls, sel);
        if (imp) return imp;
    }

在上面cache传NO,其实也好理解正因为类里cache没有所以才会走到慢速方法查找流程.

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
    }

在这里会进行一系列的判断,判断是不是类的initialize的方法.显然不是所以继续往下走:

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

在这里会调用getMethodNoSuper_nolock(cls, sel)来找方法.我们来看下

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


    assert(cls->isRealized());
    // fixme nil cls? 
    // fixme nil sel?


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


    return nil;
}

看到这我们就一目了然了,首先class里data()找到methods的beginLists和class里data()找到methods的endLists,然后遍历search_method_list 用sel当做索引去寻找,如果找到方法就返回.并且log_and_fill_cache 存一份存进缓存里 .

 log_and_fill_cache(cls, meth->imp, sel, inst, cls);
 imp = meth->imp;            
 goto done;

再看下search_method_list的代码:

static method_t *search_method_list(const method_list_t *mlist, SEL sel)
{
    int methodListIsFixedUp = mlist->isFixedUp();
    int methodListHasExpectedSize = mlist->entsize() == sizeof(method_t);
    
    if (__builtin_expect(methodListIsFixedUp && methodListHasExpectedSize, 1)) {
        return findMethodInSortedMethodList(sel, mlist);
    } else {
        // Linear search of unsorted method list
        for (auto& meth : *mlist) {
            if (meth.name == sel) return &meth;
        }
    }

#if DEBUG
    // sanity-check negative results
    if (mlist->isFixedUp()) {
        for (auto& meth : *mlist) {
            if (meth.name == sel) {
                _objc_fatal("linear search worked when binary search did not");
            }
        }
    }
#endif

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

首先

 for (Class curClass = cls->superclass;
             curClass != nil;
             curClass = curClass->superclass)

找到当前类的superclass,在superclass里 调用 getMethodNoSuper_nolock(curClass, sel);

在superclass的方法列表里去search,找到的话 log_and_fill_cache(cls, meth->imp, sel, inst, curClass); 进行存储方便下次寻找,如果superclass没找到继续往上找,即最后是NSObject.这就解释了2.2 对象调用父类的方法、2.3 对象调用父类的父类方法.

但是接着上面所说继续下去找到NSObject在它的方法列表里依然没有找到相应的imp会怎么样?

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

其实这里的流程就到下篇要讲的内容消息转发机制.我们可以稍微的看一下:

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

在这里有个判断cls->isMetaClass(),判断当前的类是不是元类,因为现在是对象方法不是元类,所以为false,会进入 _class_resolveInstanceMethod(cls, sel, inst);

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




/***********************************************************************
* _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);
        }
    }
}
 

走到下面:

imp = (IMP)_objc_msgForward_impcache;

 cache_fill(cls, sel, imp, inst);
看下_objc_msgForward_impcache


发现看不到,工程全局搜索_objc_msgForward_impcache,发现很多地方都引用它了,但是基本都是调用,而真正实现的地方在汇编里


看到进入_objc_msgForward_impcache 会执行 b __objc_msgForward (b代表跳转)

看下__objc_msgForward


看到个__objc_forward_handler 我们来搜索__objc_forward_handler,发现搜不出什么信息,在这要说下一般汇编中带两个下划线__ 底层一般是以C函数来实现的,我们一般去掉一个下划线搜索


发现 void *_objc_forward_handler = (void*)objc_defaultForwardHandler;而  (void*)objc_defaultForwardHandler;的实现就在上面

看在这里是不是很熟悉了,看到了熟悉的系统报错 unrecognized selector sent to instance

 所以方法找不到时,控制台报的 unrecognized selector sent to instance 0x100583970,

就是在这里调用的.

所有的一切一切是不是都得到了验证.

类方法的查找其实和上面是一样的,只不过class传的就是metaClass 元类了.不过在这里有个容易出错的地方:

假如 LGStudent 、LGPerson 没有实现 +(void)sayNice 的类方法,而NSObject里实现了 - (void)sayNice,

请问 用LGStudent 和 LGPerson 调用 结果如何?

[LGStudent sayNice]; [LGPerson sayNice]; 其实感兴趣的盆友可以试一下,其实结果都不会崩溃,因为就如同我探索前所说的类方法存在元类里,想当于元类调用sayNice 的实例方法,如果元类找不到就会找元类的父类最后到根元类 最后到NSObject.下面把那对象之间的关系以及ISA的走位图给大家.

四 .总结

  • 消息的慢速查找流程首先从类和父类的缓存列表进行查找,然后在方法列表进行查找。
  • 直到查找到NSObject中都没有找到方法,进行方法解析,消息转发处理失败之后则报经典错误错unrecognized selector sent to instance