OC底層探索12-消息動(dòng)態(tài)決議,方法慢速、快速轉(zhuǎn)發(fā)

OC底層探索11-objc_msgSend慢速查找流程中解釋了對(duì)方法的非緩存查詢以及方法查找失敗之后的系統(tǒng)報(bào)錯(cuò)。

如果在2種機(jī)制下都沒有找到方法imp,蘋果也給出了2條建議:

  • 動(dòng)態(tài)方法決議:慢速查找流程未找到后,會(huì)執(zhí)行一次動(dòng)態(tài)方法決議resolveMethod_locked
  • 消息轉(zhuǎn)發(fā):如果動(dòng)態(tài)方法決議仍然沒有找到實(shí)現(xiàn),則進(jìn)行消息轉(zhuǎn)發(fā)

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

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

    runtimeLock.unlock();
    // 動(dòng)態(tài)方法決議 : 給一次機(jī)會(huì) 重新查詢
    if (! cls->isMetaClass()) {  // 對(duì)象 - 類
        resolveInstanceMethod(inst, sel, cls);
    } 
    else { // 類方法 - 元類
        resolveClassMethod(inst, sel, cls);
        if (!lookUpImpOrNil(inst, sel, cls)) { 
        // 此處cls已經(jīng)是元類了。元類調(diào)用方法,根據(jù)isa關(guān)系會(huì)在根元類中查找方法。
        //通過根元類的繼承鏈最終找到NSObject,在NSObject中查詢`實(shí)例方法`的resolve的實(shí)現(xiàn)。巧妙的設(shè)計(jì)!!
            resolveInstanceMethod(inst, sel, cls);
        }
    }

    // 在調(diào)用一次查詢
    return lookUpImpOrForward(inst, sel, cls, behavior | LOOKUP_CACHE);
}
  • 此處的cls已經(jīng)是指向isa的指針,也就是,元類,此邏輯是經(jīng)過匯編層的加工。所以判斷不是元類就調(diào)用實(shí)例方法的resolve實(shí)現(xiàn);反之調(diào)用類方法的resolve實(shí)現(xiàn).
  • 根據(jù)元類的iSA關(guān)系,最終會(huì)找到根元類(NSObject),因?yàn)轭惖母惗际?code>NSObject。但(NSObject)類中只存在對(duì)象方法,所以需要再調(diào)用一次resolveInstanceMethod
  • 在方法動(dòng)態(tài)決議中,開發(fā)者會(huì)重新實(shí)現(xiàn)該selimp所以, 需要重新進(jìn)行一次查詢。而且在本次查詢中會(huì)優(yōu)先在緩存中查找。
resolveInstanceMethod

實(shí)例方法的resolve具體源碼實(shí)現(xiàn)

static void resolveInstanceMethod(id inst, SEL sel, Class cls)
{
    runtimeLock.assertUnlocked();
    ASSERT(cls->isRealized());
    //生成動(dòng)態(tài)解析的方法Sel
    SEL resolve_sel = @selector(resolveInstanceMethod:);

    //在當(dāng)前類的元類中查找resolve方法是否找得到
    if (!lookUpImpOrNil(cls, resolve_sel, cls->ISA())) {
        return;
    }
    
    //resolveInstanceMethod消息發(fā)送
    BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
    bool resolved = msg(cls, resolve_sel, sel);

    //進(jìn)行一次方法慢速查詢,將當(dāng)前方法插入緩存中,提高效率
    IMP imp = lookUpImpOrNil(inst, sel, cls);
    //在實(shí)現(xiàn)且需要打印是,進(jìn)行打印
    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 {
            _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));
        }
    }
}
  • resolveInstanceMethod調(diào)用后,又調(diào)用了一次lookUpImpOrNil;我們知道該方法如果找到對(duì)應(yīng)imp之后會(huì)插入到對(duì)象類的緩存中去,方便后續(xù)使用;另一個(gè)是方便debug進(jìn)行打印。

如何打開該打印
可以通過查看log來發(fā)現(xiàn)實(shí)現(xiàn)了resolveInstanceMethod的方法

resolveClassMethod

類方法的resolve具體源碼實(shí)現(xiàn)

static void resolveClassMethod(id inst, SEL sel, Class cls)
{
    runtimeLock.assertUnlocked();
    ASSERT(cls->isRealized());
    ASSERT(cls->isMetaClass());
    //判斷resolveClassMethod方法是否實(shí)現(xiàn)
    if (!lookUpImpOrNil(inst, @selector(resolveClassMethod:), cls)) {
        return;
    }
    //判斷類是否實(shí)現(xiàn)
    Class nonmeta;
    {
        mutex_locker_t lock(runtimeLock);
        //根據(jù)元類找到類
        nonmeta = getMaybeUnrealizedNonMetaClass(cls, inst);
        if (!nonmeta->isRealized()) {
            _objc_fatal("nonmeta class %s (%p) unexpectedly not realized",
                        nonmeta->nameForLogging(), nonmeta);
        }
    }
    //resolveClassMethod消息發(fā)送
    BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
    bool resolved = msg(nonmeta, @selector(resolveClassMethod:), sel);

    //進(jìn)行一次方法慢速查詢,將當(dāng)前方法插入緩存中,提高效率
    IMP imp = lookUpImpOrNil(inst, sel, cls);
    
    //在實(shí)現(xiàn)且需要打印是,進(jìn)行打印
    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));
        }
    }
}

  • 實(shí)現(xiàn)邏輯于resolveInstanceMethod基本相同
使用方式
  1. 實(shí)例方法的resolve
    為self動(dòng)態(tài)增加sayMaster的實(shí)現(xiàn).使用runtime-api
+ (BOOL)resolveInstanceMethod:(SEL)sel{
    IMP imp           = class_getMethodImplementation(self, @selector(sayMaster));
    Method sayMMethod = class_getInstanceMethod(self, @selector(sayMaster));
    const char *type  = method_getTypeEncoding(sayMMethod);
    return class_addMethod(self, sel, imp, type);
}
  1. 類方法的resolve
+ (BOOL)resolveClassMethod:(SEL)sel{
    IMP imp           = class_getMethodImplementation(objc_getMetaClass("Person"), @selector(lgClassMethod));
        Method sayMMethod = class_getInstanceMethod(objc_getMetaClass("Person"), @selector(lgClassMethod));
        const char *type  = method_getTypeEncoding(sayMMethod);
        return class_addMethod(objc_getMetaClass("Person"), sel, imp, type);
}
  • 唯一的區(qū)別就是類方法是需要添加到元類中的,所以需要先找到元類objc_getMetaClass.
根據(jù)觀察resolveInstanceMetho會(huì)走2次
  1. 第一次是在查詢方法時(shí)lookupimp中調(diào)用的

  2. 第二次是在coreFunction時(shí)調(diào)用的


  • 在慢速轉(zhuǎn)發(fā)過程中會(huì)進(jìn)行第二次調(diào)用,后面會(huì)換種方式來驗(yàn)證

2.消息轉(zhuǎn)發(fā)

在之前有提到apple推薦的快速轉(zhuǎn)發(fā)、慢速轉(zhuǎn)發(fā),他們是何時(shí)調(diào)用的呢?是以什么方式調(diào)用的呢?現(xiàn)在就來討論下~

方法一
不知在之前有沒有留意一個(gè)方法log_and_fill_cache(...)在這個(gè)方法中我們發(fā)現(xiàn)了一個(gè)系統(tǒng)提供的log方法。
static void
log_and_fill_cache(Class cls, IMP imp, SEL sel, id receiver, Class implementer)
{
#if SUPPORT_MESSAGE_LOGGING
    if (slowpath(objcMsgLogEnabled && implementer)) {
        bool cacheIt = logMessageSend(implementer->isMetaClass(), 
                                      cls->nameForLogging(),
                                      implementer->nameForLogging(), 
                                      sel);
        if (!cacheIt) return;
    }
#endif
    cache_fill(cls, sel, imp, receiver);
}
  • 是否打印就是看objcMsgLogEnabled這個(gè)參數(shù)的值,系統(tǒng)并沒有對(duì)外提供這個(gè)方法,但是我們可以自己導(dǎo)出.
調(diào)用方法
  • extern void instrumentObjcMessageSends(BOOL flag);這個(gè)方法就是系統(tǒng)提供,但是需要我們手動(dòng)導(dǎo)出后使用的方法。
  • 在我們調(diào)用方法的前后進(jìn)行,可以看到該方法的所有調(diào)用記錄
如何查看
結(jié)果

看到了熟悉的resolveInstanceMethod,而且出現(xiàn)了2次,也印證了之前的猜測(cè)。
與此同時(shí)還有些并不熟悉的方法forwardingTargetForSelector,methodSignatureForSelector。

方法二

看到了這個(gè)調(diào)用的堆棧信息,調(diào)用的是CoreFoundation庫。可是apple爸爸并沒有開源這個(gè)庫,所以想要查看內(nèi)部的調(diào)用就需要拿出最終大招Hopper反匯編。

  • 在lldb調(diào)試中使用image list查看CoreFoundation的庫的本地地址。然后拖入Hopper中。
  • forwarding在可以看到forwardingTargetForSelector的偽代碼調(diào)用
  • 這就是所謂快速轉(zhuǎn)發(fā)流程

  • 繼續(xù)跟流程會(huì)走到methodSignatureForSelector,以及forwardInvocation
  • 這就是所謂慢速轉(zhuǎn)發(fā)流程
  • 查看調(diào)用棧這兩個(gè)方法中間應(yīng)該會(huì)調(diào)用resolveInstanceMethod,但是在反匯編中沒有看到具體的調(diào)用,如果有知道的大佬可以提醒一下小弟。
消息轉(zhuǎn)發(fā)簡(jiǎn)單實(shí)現(xiàn)
// 1: 快速轉(zhuǎn)發(fā)
- (id)forwardingTargetForSelector:(SEL)aSelector{
    NSLog(@"%s - %@",__func__,NSStringFromSelector(aSelector));
    //此處返回一個(gè)實(shí)現(xiàn)該方法sel的對(duì)象
    return [super forwardingTargetForSelector:aSelector];
}

// 2: 慢速轉(zhuǎn)發(fā)
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
    NSLog(@"%s - %@",__func__,NSStringFromSelector(aSelector));
    //返回方法的參數(shù)編碼
    return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}

- (void)forwardInvocation:(NSInvocation *)anInvocation{
    NSLog(@"%s - %@",__func__,anInvocation);
    //更換方法接受者
    anInvocation.target = [LGStudent alloc];
    //更換方法索引
    anInvocation.selector = NSSelectorFromString(@"實(shí)現(xiàn)了該IMP的SEL");
    //更換方法參數(shù)編碼
    anInvocation.methodSignature = [NSMethodSignature signatureWithObjCTypes:"v@:"]
    // anInvocation 保存 - 方法
    [anInvocation invoke];
}

3. 整體流程圖

消息實(shí)現(xiàn)未找到后的流程
  • forwardInvocation不處理并不會(huì)奔潰。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容