論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
相關文章
相關標籤/搜索