iOS底層原理_10消息動(dòng)態(tài)決議

第十節(jié)課 消息動(dòng)態(tài)決議

在經(jīng)過我們的快速與慢速查找之后依舊沒有找到的怎么辦呢?
cache_getImp的父類查找流程中LGETImpMissDynamic返回為空的情況下,并沒有直接慢速查找,而是進(jìn)行遞歸查找父類的父類的緩存,都查不到的情況下最終再往下繼續(xù)

unrecognized selector sent to instance

相信大多人都看過這個(gè)錯(cuò)誤吧?如果沒看過當(dāng)我沒說??
調(diào)用未實(shí)現(xiàn)的對(duì)象方法會(huì)報(bào)出這個(gè)錯(cuò)誤,但是我們只知道報(bào)錯(cuò),從來沒有去研究過是怎么報(bào)的這個(gè)錯(cuò)誤,接下來我們就來看看底層報(bào)錯(cuò)的實(shí)現(xiàn)原理

全局搜索lookUpImpOrForward
我們根據(jù)之前的查找流程已經(jīng)知道了,最終如果沒有找到的話,是返回的一個(gè)forward_imp

if (slowpath((curClass = curClass->getSuperclass()) == nil)) {
                imp = forward_imp;
                break;
            }

但是具體是什么呢?我們來往上看下賦值部分

const IMP forward_imp = (IMP)_objc_msgForward_impcache;

//進(jìn)入_objc_msgForward_impcache
    STATIC_ENTRY __objc_msgForward_impcache

    // No stret specialization.
    b   __objc_msgForward

    END_ENTRY __objc_msgForward_impcache
    
    //進(jìn)入__objc_msgForward
    ENTRY __objc_msgForward

    adrp    x17, __objc_forward_handler@PAGE
    ldr p17, [x17, __objc_forward_handler@PAGEOFF]
    TailCallFunctionPointer x17
    
    END_ENTRY __objc_msgForward
    
    //進(jìn)入TailCallFunctionPointer x17
.macro TailCallFunctionPointer
    // $0 = function pointer value
    br  $0
.endmacro

//發(fā)現(xiàn)實(shí)際就是br  $0,那我們就直接看x17-> __objc_forward_handler
//全局搜索__objc_forward_handler搜不到,就搜索objc_forward_handler

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

我們可以看到實(shí)際輸出的錯(cuò)誤原因就是這里打印的,但是這中間底層實(shí)際做了很多的事情喲,接下來我們就來詳細(xì)的剖析一下

方法動(dòng)態(tài)決議

首先,還是回到lookUpImpOrForward主函數(shù)中
來看這個(gè)函數(shù)

    // No implementation found. Try method resolver once.
    //behavior = 3
    //LOOKUP_RESOLVER = 2
    //behavior & LOOKUP_RESOLVER = 3 & 2 = 0x11 & 0x10 = 0x10 = 2
    if (slowpath(behavior & LOOKUP_RESOLVER)) {
        //behavior & LOOKUP_RESOLVER = 1 & 2 = 0x01 & 0x10 = 0x00 = 0
        behavior ^= LOOKUP_RESOLVER;
        return resolveMethod_locked(inst, sel, cls, behavior);
    }
   behavior為0后,不在進(jìn)入此代碼,保證了每一個(gè)lookUpImpOrForward函數(shù)最多只能執(zhí)行一次resolveMethod_locked(動(dòng)態(tài)方法決議),直到behavior被重新賦值

tips:在底層經(jīng)??吹揭恍┻\(yùn)算符,補(bǔ)充幾個(gè)這次用到的:
&:按位與,參與運(yùn)算的兩數(shù)各對(duì)應(yīng)的二進(jìn)位相與。只有對(duì)應(yīng)的兩個(gè)二進(jìn)位都為1時(shí),結(jié)果位才為1。

^:按位異或,比較的是二進(jìn)制位,相同位置數(shù)字不同為1,相同為0

^=:按位異或,比較的是二進(jìn)制位,相同位置數(shù)字不同為1,相同為0,將結(jié)果賦值為運(yùn)算符左邊。

進(jìn)入resolveMethod_locked

static NEVER_INLINE IMP
resolveMethod_locked(id inst, SEL sel, Class cls, int behavior)
{
    runtimeLock.assertLocked();
    ASSERT(cls->isRealized());

    runtimeLock.unlock();
    // 給你一次機(jī)會(huì) 拯救地球 -> imp
    if (! cls->isMetaClass()) {//非元類
        // try [cls resolveInstanceMethod:sel]
        //
        resolveInstanceMethod(inst, sel, cls);
    } 
    else {//元類
        // try [nonMetaClass resolveClassMethod:sel]
        // and [cls resolveInstanceMethod:sel]
        resolveClassMethod(inst, sel, cls);
        if (!lookUpImpOrNilTryCache(inst, sel, cls)) {
            resolveInstanceMethod(inst, sel, cls);
        }
    }
    
    // chances are that calling the resolver have populated the cache
    // so attempt using it
    return lookUpImpOrForwardTryCache(inst, sel, cls, behavior);
}

此函數(shù)里面有三個(gè)關(guān)鍵的函數(shù):

resolveInstanceMethod:實(shí)例方法動(dòng)態(tài)添加imp

resolveClassMethod:類方法動(dòng)態(tài)添加imp

lookUpImpOrForwardTryCache,當(dāng)完成添加之后,回到之前的慢速查找流程再來一遍

resolveInstanceMethod

static void resolveInstanceMethod(id inst, SEL sel, Class cls)
{
    runtimeLock.assertUnlocked();
    ASSERT(cls->isRealized());
    SEL resolve_sel = @selector(resolveInstanceMethod:);
    //不會(huì)進(jìn)入return,因?yàn)橄到y(tǒng)有默認(rèn)實(shí)現(xiàn)resolveInstanceMethod
    if (!lookUpImpOrNilTryCache(cls, resolve_sel, cls->ISA(/*authenticated*/true))) {
        // Resolver not implemented.
        return;
    }
    //系統(tǒng)會(huì)在此處為你發(fā)送一個(gè)消息resolve_sel
    //當(dāng)你的這個(gè)類檢測(cè)了這個(gè)消息,并且做了處理
    BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
    bool resolved = msg(cls, resolve_sel, sel);

    // Cache the result (good or bad) so the resolver doesn't fire next time.
    // +resolveInstanceMethod adds to self a.k.a. cls
    //那么此時(shí)系統(tǒng)會(huì)重新查找,此函數(shù)最終會(huì)觸發(fā)LookUpImpOrForward
    IMP imp = lookUpImpOrNilTryCache(inst, sel, cls);

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

resolveClassMethod 類方法的動(dòng)態(tài)決議

static void resolveClassMethod(id inst, SEL sel, Class cls)
{
    runtimeLock.assertUnlocked();
    ASSERT(cls->isRealized());
    ASSERT(cls->isMetaClass());
    //系統(tǒng)給resolveClassMethod函數(shù)默認(rèn)返回NO,即默認(rèn)實(shí)現(xiàn)了,不會(huì)return 
    if (!lookUpImpOrNilTryCache(inst, @selector(resolveClassMethod:), cls)) {
        // Resolver not implemented.
        return;
    }
    //容錯(cuò)處理,對(duì)元類局部操作,防止沒有實(shí)現(xiàn)
    Class nonmeta;
    {
        mutex_locker_t lock(runtimeLock);
        nonmeta = getMaybeUnrealizedNonMetaClass(cls, inst);
        // +initialize path should have realized nonmeta already
        if (!nonmeta->isRealized()) {
            _objc_fatal("nonmeta class %s (%p) unexpectedly not realized",
                        nonmeta->nameForLogging(), nonmeta);
        }
    }
    //系統(tǒng)會(huì)在此處為你發(fā)送一個(gè)消息resolve_sel
    //當(dāng)你的這個(gè)類檢測(cè)了這個(gè)消息,并且做了處理
    BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
    //類的類方法,就相當(dāng)于元類的對(duì)象方法
    bool resolved = msg(nonmeta, @selector(resolveClassMethod:), sel);

    // Cache the result (good or bad) so the resolver doesn't fire next time.
    // +resolveClassMethod adds to self->ISA() a.k.a. cls
    IMP imp = lookUpImpOrNilTryCache(inst, sel, cls);

    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 resolveClassMethod:%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));
        }
    }
}

lookUpImpOrForwardTryCache

通過動(dòng)態(tài)添加方法之后,再次嘗試查找sel對(duì)應(yīng)的最新添加的imp


IMP lookUpImpOrForwardTryCache(id inst, SEL sel, Class cls, int behavior)
{
    return _lookUpImpTryCache(inst, sel, cls, behavior);
}

 ALWAYS_INLINE
static IMP _lookUpImpTryCache(id inst, SEL sel, Class cls, int behavior)
{
    runtimeLock.assertUnlocked();
    //當(dāng)類未初始化的時(shí)候,進(jìn)入lookUpImpOrForward
    //在里面處理不緩存任何方法
    if (slowpath(!cls->isInitialized())) {
        // see comment in lookUpImpOrForward
        return lookUpImpOrForward(inst, sel, cls, behavior);
    }
    //通過匯編查找
    IMP imp = cache_getImp(cls, sel);
    if (imp != NULL) goto done;
#if CONFIG_USE_PREOPT_CACHES
    //共享緩存查找
    if (fastpath(cls->cache.isConstantOptimizedCache(/* strict */true))) {
        imp = cache_getImp(cls->cache.preoptFallbackClass(), sel);
    }
#endif
    //如果依然找不到,證明方法真的不存在,也就是說,只能依靠方法的動(dòng)態(tài)添加了
    //那么此時(shí)再次進(jìn)入方法的慢速查找流程
    if (slowpath(imp == NULL)) {
        return lookUpImpOrForward(inst, sel, cls, behavior);
    }

done:
    //此判斷是當(dāng)前imp已經(jīng)存在了,并且這個(gè)imp是默認(rèn)賦值的forward_imp
    //此時(shí)返回imp為nil;
    if ((behavior & LOOKUP_NIL) && imp == (IMP)_objc_msgForward_impcache) {
        return nil;
    }
    return imp;
} 

整合實(shí)例對(duì)象/類對(duì)象的動(dòng)態(tài)方法決議

根據(jù)上述源碼分析,我們可以看到,只要我們實(shí)現(xiàn)了resolveInstanceMethod/resolveClassMethod這兩個(gè)方法,我們就可以攔截到崩潰前執(zhí)行的步驟,并做出一定改造的話,就可以攔截崩潰。

話不多說,上代碼。創(chuàng)建NSObject分類的方式將此實(shí)現(xiàn)都放在分類中:

@implementation NSObject (FF)

//為實(shí)例對(duì)象創(chuàng)建一個(gè)IMP
- (void)sayHelloCrash {
    NSLog(@"%s",__func__);
}

//為元類對(duì)象創(chuàng)建創(chuàng)建一個(gè)IMP
+ (void)sayHelloBug {
    NSLog(@"%@ - %s",self,__func__);
}

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

+ (BOOL)resolveInstanceMethod:(SEL)sel {
    
    NSLog(@"人工介入resolveInstanceMethod :%@-%@",self,NSStringFromSelector(sel));
    
    if (sel == @selector(say666)) {
        
        IMP sayHelloCrashImp = class_getMethodImplementation(self, @selector(sayHelloCrash));
        Method method = class_getInstanceMethod(self, @selector(sayHelloCrash));
        const char * type = method_getTypeEncoding(method);
        return  class_addMethod(self, sel, sayHelloCrashImp, type);
    } else if (sel == @selector(sayHappy)) {
        
        IMP enjoyTimeImp = class_getMethodImplementation(objc_getMetaClass("HZMTeacher"), @selector(enjoyTime));
        Method method = class_getInstanceMethod(objc_getMetaClass("HZMPerson"), @selector(enjoyTime));
        const char * type = method_getTypeEncoding(method);
        return  class_addMethod(objc_getMetaClass("HZMPerson"), sel, enjoyTimeImp, type);
    }

    return NO;
}
#pragma clang diagnostic pop
@end

為什么要寫在NSObject分類里呢?我們來分析下
其實(shí)通過方法慢速查找流程可以發(fā)現(xiàn)其查找路徑有兩條:
實(shí)例方法:類 -- 父類 -- 根類 -- nil
類方法:元類 -- 根元類 -- 根類 -- nil
它們的共同點(diǎn)是如果前面沒找到,都會(huì)來到根類即NSObject中查找,所以我們將上述的兩個(gè)方法統(tǒng)一整合在一起,以NSObject添加分類的方式來實(shí)現(xiàn)統(tǒng)一處理。而且由于類方法的查找,在其繼承鏈,查找的也是實(shí)例方法,所以可以將實(shí)例方法 和 類方法的統(tǒng)一處理放在resolveInstanceMethod

這種方式的實(shí)現(xiàn),正好與源碼中針對(duì)類方法的處理邏輯是一致的,即完美闡述為什么調(diào)用了類方法動(dòng)態(tài)方法決議,還要調(diào)用對(duì)象方法動(dòng)態(tài)方法決議(也就是resolveInstanceMethod為什么走了兩次的原因),其根本原因還是類方法在元類中的實(shí)例方法。

當(dāng)然,上面這種寫法還是會(huì)有其他的問題,比如系統(tǒng)方法也會(huì)被更改,針對(duì)這一點(diǎn),我們可以針對(duì)自定義類中方法統(tǒng)一方法名的前綴,根據(jù)前綴來判斷是否是自定義方法,然后統(tǒng)一處理自定義方法,例如可以在崩潰前pop到首頁(yè),再上報(bào)自己的服務(wù)器告知出問題的原因,主要是用于app線上防崩潰的處理,提升用戶的體驗(yàn)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容