iOS 消息查找流程

一、前言

上一篇 從彙編探索objc_msgSend 遺留了一個 __class_lookupMethodAndLoadCache3,接下來就是對這個方法進行分析,也就會來到了消息查找流程,消息查找流程分爲快速和慢速,快速查找已經在 objc_msgSend 找過了,找不到就會進入慢速查找,慢速查找流程會在 __class_lookupMethodAndLoadCache3 方法進行,可是我先不直接分析,先來點鋪墊,想看源碼分析的能夠跳過第二步。緩存

二、isa 走位圖

咱們先建立 StudentPerson 兩個對象,Student -> Person -> NSObject(->表明繼承),而後在兩個對象以及 NSObject 分類中各自添加一個實例方法和類方法。bash

#pragma clang diagnostic push
// 讓編譯器忽略錯誤
#pragma clang diagnostic ignored "-Wundeclared-selector"

        Student *student = [[Student alloc] init];
        // 對象方法測試
        // 1.對象的實例方法 - 本身有
        [student studentSayHello];
        // 2.對象的實例方法 - 本身沒有 - 找老爸的
        [student personSayNB];
        // 3.對象的實例方法 - 本身沒有 - 老爸沒有 - 找老爸的老爸 - NSObject
        [student nsobjectSayMaster];
        // 4.對象的實例方法 - 本身沒有 - 老爸沒有 - 找老爸的老爸 -> NSObject 也沒有 - 奔潰
        // [student performSelector:@selector(saySomething)];
        
        // 類方法測試
        // 5.類方法 - 本身有
        [Student studentSayObjc];
        // 6.類方法 - 本身沒有 - 老爸有
        [Student personSayHappay];
        // 7.類方法 - 本身沒有 - 找老爸的老爸 - NSObject
        [Student nsobjectSayEasy];
        // 8.類方法- 本身沒有 - 老爸沒有 - 找老爸的老爸 -> NSObject 也沒有 - 奔潰
        // [LGStudent performSelector:@selector(sayLove)];
        // 9.類方法- 本身沒有 - 老爸沒有 - 找老爸的老爸 -> NSObject 也沒有 - 可是有對象方法,能走不會奔潰
        [Student nsobjectSayEasy];

#pragma clang diagnostic pop
複製代碼

上面幾個方法的調用結果我已經寫到方法上面了,可是第9點,爲何類調用 NSObject 的對象方法可以成功,這就須要一個很牛皮的圖了,isa走位圖,下面奉上,因爲類方法會保存在元類的對象方法列表裏,因此類調用類方法時,會去元類的對象方法列表裏面找,沒找到再去根元類裏找,根元類的父類是 NSObject,因此會找到 NSObject 裏,因此 Student 最終會在 NSObject 對象方法列表裏找到 nsobjectSayEasy 進行調用。 多線程

isa流程圖

三、消息查找流程

1.__class_lookupMethodAndLoadCache3 方法分析

當彙編的 objc_msgSend 在緩存沒有找到方法時,最終會走到這個方法進行慢速查找方法。app

IMP _class_lookupMethodAndLoadCache3(id obj, SEL sel, Class cls)
{
    /*
     cls:若是是實例方法那就是類,若是是類方法那就是元類
     sel:方法名
     obj:方法調用者
     */
    return lookUpImpOrForward(cls, sel, obj, 
                              YES/*initialize*/, NO/*cache*/, YES/*resolver*/);
}
複製代碼
2.lookUpImpOrForward 方法查找流程
IMP lookUpImpOrForward(Class cls, SEL sel, id inst, 
                       bool initialize, bool cache, bool resolver)
{
    IMP imp = nil;
    bool triedResolver = NO;
    runtimeLock.assertUnlocked();

    // 根據外界傳來的值進行判斷,這裏爲 NO,不走
    if (cache) {
        imp = cache_getImp(cls, sel);
        if (imp) return imp;
    }
    // 防止多線程訪問兩個方法出現返回imp錯誤
    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();

    // 類初始化後再對緩存進行一次查找,我的認爲蘋果爸爸仍是很嚴謹的
    imp = cache_getImp(cls, sel);
    if (imp) goto done;
   
    {
        // 在當前類中找方法,裏面會經過二分查找節省時間,有興趣的能夠本身去看看,在 search_method_list 這個方法裏面
        Method meth = getMethodNoSuper_nolock(cls, sel);
        if (meth) {
            // 找到了進行緩存一份
            log_and_fill_cache(cls, meth->imp, sel, inst, cls);
            imp = meth->imp;
            goto done;
        }
    }

    {
        // 當本類找不到的時候,就會去父類緩存查找,若是沒有在遞歸查找
        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.");
            }
            
            // 看看父類的緩存裏面是否存在該方法
            imp = cache_getImp(curClass, sel);
            if (imp) {
                if (imp != (IMP)_objc_msgForward_impcache) {
                    // 找到後進行緩存
                    log_and_fill_cache(cls, imp, sel, inst, curClass);
                    goto done;
                }
                else {
                    break;
                }
            }
            
            // 沒有緩存就去父類的方法列表慢速查找
            Method meth = getMethodNoSuper_nolock(curClass, sel);
            if (meth) {
                log_and_fill_cache(cls, meth->imp, sel, inst, curClass);
                imp = meth->imp;
                goto done;
            }
        }
    }

    // 若是本類和父類以及父父類都沒找到,就會進行動態方法決議
    if (resolver  &&  !triedResolver) {
        runtimeLock.unlock();
        _class_resolveMethod(cls, sel, inst);
        runtimeLock.lock();
        // 此處代碼只會走一次,由於 triedResolver 會設置爲 YES
        triedResolver = YES;
        goto retry;
    }

    // No implementation found, and method resolver didn’t help. 
    // 若是本類及父類都沒有找到,動態方法決議也沒有幫助,就會調用 _objc_msgForward_impcache 方法
    imp = (IMP)_objc_msgForward_impcache;
    cache_fill(cls, sel, imp, inst);

 done:
    runtimeLock.unlock();
    return imp;
}
複製代碼
  • 總結:上述總體流程能夠發現,方法的查找流程先會去本類找,再去父類以及父父類裏找,若是都沒有找到,就會進行 _class_resolveMethod 動態方法決議(動態方法決議下篇博客會分析到),若是上述都沒有幫助,則會調用 _objc_msgForward_impcache 方法,這個方法又是作什麼的呢?立刻介紹。
3._objc_msgForward_impcache 探索

經過全局搜索,一步步查找來到 objc-msg-arm64.s 彙編文件中找到下面彙編代碼,以後會調用 __objc_forward_handler函數

STATIC_ENTRY __objc_msgForward_impcache
	// No stret specialization.
	b	__objc_msgForward
	END_ENTRY __objc_msgForward_impcache

	ENTRY __objc_msgForward
	adrp	x17, __objc_forward_handler@PAGE
	ldr	p17, [x17, __objc_forward_handler@PAGEOFF]
	TailCallFunctionPointer x17
	END_ENTRY __objc_msgForward
複製代碼

因爲這個函數是 C 函數,咱們去掉前面一個 _ 進行全局搜索_objc_forward_handler,就能找到沒有實現方法所形成的崩潰緣由了,哈哈哈哈,是否是很熟悉,unrecognized selector sent to instance 這個崩潰緣由常常遇到,終於找到底層源碼在哪了。源碼分析

__attribute__((noreturn)) void 
objc_defaultForwardHandler(id self, SEL sel)
{
    _objc_fatal("%c[%s %s]: unrecognized selector sent to instance %p "
                "(no message forward handler is installed)", 
                class_isMetaClass(object_getClass(self)) ? '+' : '-', 
                object_getClassName(self), sel_getName(sel), self);
}
void *_objc_forward_handler = (void*)objc_defaultForwardHandler;
複製代碼

四、總結

  • 1.當在 objc_msgSend 緩存中沒有找到方法,就會來到 class_lookupMethodAndLoadCache3 進行慢速查找流程。
  • 2.在 lookUpImpOrForward 裏面會先去本類當中查找方法 getMethodNoSuper_nolock,本類沒有找到就會去遞歸的去父類當中查找。
  • 3.若是本類和父類都沒有找到,就會進行動態方法決議 _class_resolveMethod,這是蘋果爸爸給咱們的最後一次機會。
  • 4.動態方法咱們還不處理,最後就會走到 _objc_forward_handler,而後崩潰報錯 selector sent to instance
相關文章
相關標籤/搜索