论objc_msgSend消息机制之消息查找

一.探索前需知

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

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

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

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

对象方法:post

2.1 对象调用本身的方法ui

LGStudent *student = [[LGStudent alloc] init];
 [student sayHello];
复制代码

2.2 对象调用父类的方法this

[student sayNB];复制代码

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

[student sayMaster];复制代码

2.4 若是调用的方法并无在本类、父类以及NSObject里实现就会报经典的错误spa

[student performSelector:@selector(sayLove)];复制代码

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

类方法:

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
相关文章
相关标签/搜索