010-消息轉(zhuǎn)發(fā)

目前遺留的問題

  1. copy和strong修飾符的區(qū)別(objc_setProperty和內(nèi)存平移, objc_getProperty都在什么情況下會調(diào)用)
  2. alloc的objc_alloc, objc_opt_class和objc_opt_isKindOfClass的符號查找
  3. 為什么第一次加載的時(shí)候firstSubclass=nil, 在執(zhí)行或者調(diào)用了LGTeacher之后, 就會有firstSubclass=LGTeacher的賦值.

上一篇章遺留問題

    1. 動態(tài)方法決議走完之后, 源碼中查詢不到后續(xù)的流程了 , 是否就已經(jīng)完畢了?
    1. 關(guān)于Class方法, 看源碼邏輯先發(fā)送resolveClassMethod消息, 根據(jù)lookUpImpOrNilTryCache判斷是否發(fā)送resolveInstanceMethod消息. 但是實(shí)際上是不會走resolveInstanceMethod方法的.

實(shí)際上測試了之后resolveInstanceMethod方法并不是不走了, 而是元類調(diào)用了resolveInstanceMethod找到了根元類, resolveInstanceMethod在NSObject里面實(shí)現(xiàn)了, 所以使用分類重寫并打印resolveInstanceMethod. 是會出現(xiàn)打印的兩次.

如果是實(shí)例方法找不到, 在本類重寫了resolveInstanceMethod方法, 是不需要找到NSObject里面的. 因?yàn)樵谠惱锩嬷苯诱业玫? 不會在向根元類查詢.

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

事實(shí)上第一個(gè)問題的答案就是消息轉(zhuǎn)發(fā). 接下里我們新建一個(gè)項(xiàng)目:

@interface Person : NSObject
- (void)func1;
/**
 * 未實(shí)現(xiàn)
 */
- (void)instanceF;
+ (void)classF;
@end

@implementation Person
- (void)func1 {
    NSLog(@"%s", __func__);
}

+ (BOOL)resolveInstanceMethod:(SEL)sel {
    NSLog(@"%s %@", __func__, NSStringFromSelector(sel));
    return [super resolveInstanceMethod:sel];
}

@end

int main(int argc, char * argv[]) {
    Person *p = [Person alloc];
//未實(shí)現(xiàn)調(diào)用
    [p instanceF];
    return 0;
}
01.png

下面走了兩次方法動態(tài)決議之后崩潰了. 這里我們看不出什么, 打個(gè)斷點(diǎn)試一試:
整個(gè)堆棧信息如下:


02.png

可以看到第一次和第二次的堆棧調(diào)用:

  • _objc_msgSend_uncached->resolveMethod_locked->resolveInstanceMethod觸發(fā)了+[Person resolveInstanceMethod:]

第二次是

  • CoreFoundation框架的__forwarding_prep_0___->___forwarding___->-[NSObject(NSObject) methodSignatureForSelector:]->__methodDescriptionForSelector->libobjc下面的class_getInstanceMethod->resolveMethod_locked->resolveInstanceMethod觸發(fā)了+[Person resolveInstanceMethod:]

也就是說, 在我們平常開發(fā)中, 找不到方法實(shí)現(xiàn)的時(shí)候通常是會調(diào)用兩次動態(tài)決議, 才會產(chǎn)生報(bào)錯(cuò), 那么為什么會是這樣, 我也不知道. 既然第二次是CF框架, 那么我們就看看CF里面有沒有prep0看看是否能找到, 網(wǎng)址

我們把CF下載下來使用VSCode打開搜索, 發(fā)現(xiàn)不管是forwarding_prep_0_, prep, forwarding, methodSignatureForSelector通通搜不到. 所以可能是這部分沒有開源.

反匯編CoreFoundation

找CoreFoundation

03.png

找到路徑, 在文件夾中command+shift+G輸入路徑, 得到下圖:


04.png

取出CoreFoundation可執(zhí)行文件.

Hopper 反匯編CoreFoundation

hopper根據(jù)下圖選擇上面的可執(zhí)行文件, 一路確定:


05.png

得到如下頁面, 終于看到了我們想要的:


06.png

然后雙擊就可以進(jìn)入對應(yīng)的方法中:
int ____forwarding___(int arg0, int arg1) {
    rsi = arg1;
    rdi = arg0;
    r15 = rdi;
    var_30 = *___stack_chk_guard;
    rcx = COND_BYTE_SET(NE);
    if (rsi != 0x0) {
            r12 = _objc_msgSend_stret;
    }
    else {
            r12 = _objc_msgSend;
    }
    rax = rcx;
    rbx = *(r15 + rax * 0x8);
    rcx = *(r15 + rax * 0x8 + 0x8);
    var_140 = rcx;
    r13 = rax * 0x8;
    if ((rbx & 0x1) == 0x0) goto loc_649bb;

loc_6498b:
    rcx = *_objc_debug_taggedpointer_obfuscator;
    rcx = rcx ^ rbx;
    rax = rcx >> 0x1 & 0x7;
    if (rax == 0x7) {
            rcx = rcx >> 0x4;
            rax = (rcx & 0xff) + 0x8;
    }
    if (rax == 0x0) goto loc_64d48;

loc_649bb:
    var_148 = r13;
    var_138 = r12;
    var_158 = rsi;
    rax = object_getClass(rbx);
    r12 = rax;
    r13 = class_getName(rax);
    r14 = @selector(forwardingTargetForSelector:);
    if (class_respondsToSelector(r12, r14) == 0x0) goto loc_64a67;

loc_649fc:
    rdi = rbx;
    rax = _objc_msgSend(rdi, r14);
    if ((rax == 0x0) || (rax == rbx)) goto loc_64a67;

loc_64a19:
    r12 = var_138;
    r13 = var_148;
    if ((rax & 0x1) == 0x0) goto loc_64a5b;

loc_64a2b:
    rdx = *_objc_debug_taggedpointer_obfuscator;
    rdx = rdx ^ rax;
    rcx = rdx >> 0x1 & 0x7;
    if (rcx == 0x7) {
            rcx = (rdx >> 0x4 & 0xff) + 0x8;
    }
    if (rcx == 0x0) goto loc_64d45;

loc_64a5b:
    *(0x0 + r13) = rax;
    r15 = 0x0;
    goto loc_64d82;

loc_64d82:
    if (*___stack_chk_guard == var_30) {
            rax = r15;
    }
    else {
            rax = __stack_chk_fail();
    }
    return rax;

loc_64d45:
    rbx = rax;
    goto loc_64d48;

loc_64d48:
    if ((*(int8_t *)__$e48aedf37b9edb179d065231b52a648b & 0x10) != 0x0) goto loc_64ed1;

loc_64d55:
    rax = _getAtomTarget(rbx);
    r14 = rax;
    *(r15 + r13) = rax;
    ___invoking___(r12, r15);
    if (*r15 == r14) {
            *r15 = rbx;
    }
    goto loc_64d82;

loc_64ed1:
    ____forwarding___.cold.4();
    rax = *(rdi + 0x8);
    return rax;

loc_64a67:
    var_138 = rbx;
    if (strncmp(r13, "_NSZombie_", 0xa) == 0x0) goto loc_64dc1;

loc_64a8a:
    rbx = @selector(methodSignatureForSelector:);
    r14 = var_138;
    var_148 = r15;
    if (class_respondsToSelector(r12, rbx) == 0x0) goto loc_64dd7;

loc_64ab2:
    rax = _objc_msgSend(r14, rbx);
    rbx = var_158;
    if (rax == 0x0) goto loc_64e3c;

loc_64ad5:
    r12 = rax;
    rax = [rax _frameDescriptor];
    r13 = rax;
    if (((*(int16_t *)(*rax + 0x22) & 0xffff) >> 0x6 & 0x1) != rbx) {
            rax = sel_getName(var_140);
            rdx = " not";
            r8 = "";
            rcx = r8;
            if ((*(int16_t *)(*r13 + 0x22) & 0xffff & 0x40) == 0x0) {
                    rcx = rdx;
            }
            if (rbx == 0x0) {
                    r8 = rdx;
            }
            rdx = rax;
            _CFLog(0x4, @"*** NSForwarding: warning: method signature and compiler disagree on struct-return-edness of '%s'.  Signature thinks it does%s return a struct, and compiler thinks it does%s.", rdx, rcx, r8, r9, stack[2003]);
    }
    var_150 = r13;
    if (class_respondsToSelector(object_getClass(r14), @selector(_forwardStackInvocation:)) == 0x0) goto loc_64c19;

loc_64b6c:
    if (*____forwarding___.onceToken != 0xffffffffffffffff) {
            dispatch_once(____forwarding___.onceToken, ^ { /* block implemented at ______forwarding____block_invoke */ });
    }
    r15 = [NSInvocation requiredStackSizeForSignature:r12];
    rsi = *____forwarding___.invClassSize;
    rsp = rsp - ___chkstk_darwin();
    r13 = rsp;
    __bzero(r13, rsi);
    rbx = rsp - ___chkstk_darwin();
    objc_constructInstance(*____forwarding___.invClass, r13);
    var_140 = r15;
    [r13 _initWithMethodSignature:r12 frame:var_148 buffer:rbx size:r15];
    [var_138 _forwardStackInvocation:r13];
    r14 = 0x1;
    goto loc_64c76;

loc_64c76:
    if (*(int8_t *)(r13 + 0x34) != 0x0) {
            rax = *var_150;
            if (*(int8_t *)(rax + 0x22) < 0x0) {
                    rcx = *(int32_t *)(rax + 0x1c);
                    rdx = *(int8_t *)(rax + 0x20) & 0xff;
                    memmove(*(rdx + var_148 + rcx), *(rdx + rcx + *(r13 + 0x8)), *(int32_t *)(*rax + 0x10));
            }
    }
    rax = [r12 methodReturnType];
    rbx = rax;
    rax = *(int8_t *)rax;
    if ((rax != 0x76) && (((rax != 0x56) || (*(int8_t *)(rbx + 0x1) != 0x76)))) {
            r15 = *(r13 + 0x10);
            if (r14 != 0x0) {
                    r15 = [[NSData dataWithBytes:r15 length:var_140] bytes];
                    [r13 release];
                    rax = *(int8_t *)rbx;
            }
            if (rax == 0x44) {
                    asm{ fld        tword [r15] };
            }
    }
    else {
            r15 = ____forwarding___.placeholder;
            if (r14 != 0x0) {
                    [r13 release];
            }
    }
    goto loc_64d82;

loc_64c19:
    r15 = @selector(forwardInvocation:);
    if (class_respondsToSelector(object_getClass(r14), r15) == 0x0) goto loc_64ec2;

loc_64c3b:
    rax = [NSInvocation _invocationWithMethodSignature:r12 frame:var_148];
    r13 = rax;
    _objc_msgSend(0x0, r15);
    var_140 = 0x0;
    r14 = 0x0;
    goto loc_64c76;

loc_64ec2:
    rdi = var_130;
    ____forwarding___.cold.3(rdi, r14);
    goto loc_64ed1;

loc_64e3c:
    rax = sel_getName(var_140);
    r14 = rax;
    rax = sel_getUid(rax);
    if (rax != var_140) {
            r8 = rax;
            _CFLog(0x4, @"*** NSForwarding: warning: selector (%p) for message '%s' does not match selector known to Objective C runtime (%p)-- abort", var_140, r14, r8, r9, stack[2003]);
    }
    rbx = @selector(doesNotRecognizeSelector:);
    if (class_respondsToSelector(object_getClass(var_138), rbx) == 0x0) {
            ____forwarding___.cold.2(var_138);
    }
    rax = _objc_msgSend(var_138, rbx);
    asm{ ud2 };
    return rax;

loc_64dd7:
    rbx = class_getSuperclass(r12);
    r14 = object_getClassName(r14);
    if (rbx == 0x0) {
            _CFLog(0x4, @"*** NSForwarding: warning: object %p of class '%s' does not implement methodSignatureForSelector: -- did you forget to declare the superclass of '%s'?", var_138, r14, object_getClassName(var_138), r9, stack[2003]);
    }
    else {
            _CFLog(0x4, @"*** NSForwarding: warning: object %p of class '%s' does not implement methodSignatureForSelector: -- trouble ahead", var_138, r14, r8, r9, stack[2003]);
    }
    goto loc_64e3c;

loc_64dc1:
    ____forwarding___.cold.1(var_138, r13, var_140, rcx, r8);
    goto loc_64dd7;
}

反匯編的代碼很長, 直接看的話很懵逼, 我們一步一步挑重點(diǎn)還原一下, 細(xì)節(jié)不一定準(zhǔn)確, 只是大致流程.

void ____forwarding___(Class cls, SEL sel) {
    id receiver;
//loc_649bb
    if (class_respondsToSelector(cls, @selector(forwardingTargetForSelector:))) {
        receiver = objc_msgSend(cls, @selector(forwardingTargetForSelector:));
    }
//loc_649fc
    if (!receiver) {
        objc_msgSend(receiver, sel);
    } else {
//loc_64a8a
        if (class_respondsToSelector(cls, @selector(methodSignatureForSelector:))) {
            //loc_64ab2:
            objc_msgSend(cls, @selector(methodSignatureForSelector:));
            //loc_64ad5:
            if (class_respondsToSelector(cls, @selector(_forwardStackInvocation:))) { // 一般不實(shí)現(xiàn), 先不管
                //loc_64b6c:
                objc_msgSend(cls, @selector(_forwardStackInvocation:));
                
            } else {
                //loc_64c19:
                if (class_respondsToSelector(cls, @selector(forwardInvocation:))) {
                    rax  = NSInvocation *
                    _objc_msgSend(0x0, @selector(forwardInvocation:));
                  //相當(dāng)于執(zhí)行了這個(gè)_objc_msgSend(rax, sel)
                  ret
                }
            }
        } else {
            goto loc_64e3c;
        }
    }
    
loc_64e3c    
if (class_respondsToSelector(cls, @selector(doesNotRecognizeSelector:))) {
        objc_msgSend(cls, @selector(doesNotRecognizeSelector:));
    };
}

大概就是這么個(gè)流程, 不過在流程里我們看到了我們今天要探索的:

  • 快速轉(zhuǎn)發(fā)forwardingTargetForSelector
  • 慢速轉(zhuǎn)發(fā)methodSignatureForSelector-forwardInvocation

快速轉(zhuǎn)發(fā)

07.png

我們實(shí)現(xiàn)了方法forwardingTargetForSelector之后, 發(fā)現(xiàn)確實(shí)如prep_0一樣,
其實(shí)我們可以這樣去驗(yàn)證反匯編, 就是我們打斷點(diǎn). 如下:
08.png

反匯編的寄存器的值, 在這里每個(gè)階段都可以讀取到, 然后再去配合著去讀就會舒服很多.

我們用Proxy類實(shí)現(xiàn)instanceF方法, 然后在快速轉(zhuǎn)發(fā)中返回會發(fā)生什么呢?

09.png

不會崩潰了, 而且第二的轉(zhuǎn)發(fā)也不會走了, 問題解決了. 所以我們在第一層只需要返回一個(gè)id消息接受者, 接受者就會自動調(diào)用objc_msdSend然后繼續(xù)進(jìn)行消息處理.

從反匯編看到doesNotRecognizeSelector所以我有點(diǎn)好奇, 我打印了一下如下:

10.png

也就是說, 寫一個(gè)NSObject一個(gè)分類, 重寫doesNotRecognizeSelector可以看到一次打印.
我們下面繼續(xù)實(shí)現(xiàn)一下慢速轉(zhuǎn)發(fā)的兩個(gè)方法.

慢速轉(zhuǎn)發(fā)

methodSignatureForSelectorforwardInvocation是結(jié)合著使用的, 單獨(dú)使用任何一個(gè)都沒有用.

  • methodSignatureForSelector返回方法簽名
  • forwardInvocation拿到方法簽名, 設(shè)置接收者去實(shí)現(xiàn), 或者不處理.
    先看一下處理的效果:
    11.png

    神奇吧, 沒有處理也沒有崩潰.
    但是看到圖11的打印順序會不會很奇怪, 第一次只走到了methodSignatureForSelector就沒有繼續(xù)往下走了, 然后繼續(xù)調(diào)用了resolveInstanceMethod然后_forwardStackInvocation, 最后forwardInvocation.

也就是說, 消息轉(zhuǎn)發(fā)的流程另外一種情況如下

  • 1.實(shí)現(xiàn)了methodSignatureForSelector, 并且返回了對應(yīng)的方法簽名.
  • 2.實(shí)現(xiàn)了forwardInvocation方法,
    在第二次進(jìn)行轉(zhuǎn)發(fā)的消息是_forwardStackInvocation(如果_forwardStackInvocation沒有實(shí)現(xiàn), 則需要forwardInvocation去響應(yīng)), 不然就報(bào)錯(cuò).

我們在回到匯編中看一下:


16.png

大致就是這么個(gè)流程, 雖然畫圖技術(shù)拙劣但是已經(jīng)盡量的表達(dá)清楚了.

在慢速轉(zhuǎn)發(fā)中, 主要的點(diǎn)就是

  • methodSignatureForSelectorforwardInvocation一起搭配使用.
  • methodSignatureForSelector有正確返回值的準(zhǔn)確情況下, 會走_forwardStackInvocationforwardInvocation只需要實(shí)現(xiàn)方法即不會崩潰.
  • methodSignatureForSelector沒有返回值的情況下, forwardInvocation是無效的. 怎么處理都沒有用.

繼續(xù)看下面兩張圖:


18.png
19.png

圖18和19, 只有一個(gè)區(qū)別就是一個(gè)是強(qiáng)引用了一個(gè)Proxy的實(shí)例對象, 另外一個(gè)是局部變量, 在這個(gè)地方如果使用局部變量會被釋放掉, 報(bào)出對象的野指針錯(cuò)誤, 所以在這個(gè)地方調(diào)用的話, 要注意一點(diǎn).

Thread 1: "-[Person instanceF]: unrecognized selector sent to instance 0x600000988160"

類的過程

類方法如下, 探究過程一樣

+ (BOOL)resolveClassMethod:(SEL)sel {
    NSLog(@"%s %@", __func__, NSStringFromSelector(sel));
    return [super resolveClassMethod:sel];
}

+ (id)forwardingTargetForSelector:(SEL)aSelector {
    NSLog(@"%s %@", __func__, NSStringFromSelector(aSelector));
    return [super methodSignatureForSelector:aSelector];
}

+ (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    NSLog(@"%s %@", __func__, NSStringFromSelector(aSelector));
    if (aSelector == @selector(classF)) {
        NSMethodSignature *ms = [NSMethodSignature signatureWithObjCTypes:"v@:"];
        return ms;
    }
    return [super methodSignatureForSelector:aSelector];
}

+ (void)forwardInvocation:(NSInvocation *)anInvocation {
    NSLog(@"%s %@", __func__, NSStringFromSelector(anInvocation.selector));
}

第二次流程

蘋果顯然是設(shè)計(jì)的消息流程, 給了兩次轉(zhuǎn)發(fā)的機(jī)會, 但是第二次的轉(zhuǎn)發(fā)在原文最上面看到了這么一個(gè)調(diào)用methodDescriptionForSelector, 我們并沒有在匯編中看到, 那么它是在哪里的呢?

看圖2是怎么從methodSignatureForSelector__methodDescriptionForSelector. 做出如下改動:

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    NSLog(@"%s %@", __func__, NSStringFromSelector(aSelector));
    return nil;//返回值變?yōu)閚il
}
20.png

動態(tài)決議走了一次, 消息轉(zhuǎn)發(fā)到慢速階段后面就沒有了.

接下來我們看NSObject的定義:


21.png

這可咋辦, 啥也沒寫, 我們現(xiàn)在只知道整個(gè)二次轉(zhuǎn)發(fā)流程和[super methodSignatureForSelector]肯定是有關(guān)系的.... 不要著急, 找一份objc的源碼看看有沒有什么新收獲.源碼

22.png

23.png

看見沒, 這些都是CF的實(shí)際調(diào)用, 擴(kuò)展給了objc使用. 現(xiàn)在感覺快看到希望了, 回到反匯編繼續(xù)找CF.

void * -[NSObject methodSignatureForSelector:](void * self, void * _cmd, void * arg2) {
    rdx = arg2;
    rdi = self;
    if ((rdx != 0x0) && (___methodDescriptionForSelector([rdi class], rdx) != 0x0)) {
            rax = [NSMethodSignature signatureWithObjCTypes:rdx];
    }
    else {
            rax = 0x0;
    }
    return rax;
}

int ___methodDescriptionForSelector(int arg0, int arg1) {
//省略部分代碼
loc_7c68b:
    rax = class_getInstanceMethod(var_40, rbx);
    if (rax != 0x0) {
            rax = method_getDescription(rax);
            r15 = *rax;
            rbx = *(rax + 0x8);
    }
    else {
            rbx = 0x0;
            r15 = 0x0;
    }
    goto loc_7c6b2;
}



function class_getInstanceMethod {
    rax = _class_getInstanceMethod(rdi, rsi);
    return rax;
}

Method class_getInstanceMethod(Class cls, SEL sel)
{
    if (!cls  ||  !sel) return nil;

    // This deliberately avoids +initialize because it historically did so.

    // This implementation is a bit weird because it's the only place that 
    // wants a Method instead of an IMP.

#warning fixme build and search caches
        
    // Search method lists, try method resolver, etc.
    lookUpImpOrForward(nil, sel, cls, LOOKUP_RESOLVER);

#warning fixme build and search caches

    return _class_getMethod(cls, sel);
}

[NSObjec methodSignatureForSelector]>___methodDescriptionForSelector->>class_getInstanceMethod->objc源碼中->lookUpImpOrForward回到了消息慢速查找流程.
整個(gè)閉環(huán), 結(jié)束.

總結(jié)

消息轉(zhuǎn)發(fā)的所有流程, 不管是快速轉(zhuǎn)發(fā)還是慢速轉(zhuǎn)發(fā), 都跟__forwarding_prep_0___有關(guān), 第一次轉(zhuǎn)發(fā)和第二次轉(zhuǎn)發(fā)每一個(gè)環(huán)節(jié)變量的控制走與不走, 都需要自己進(jìn)行大量的測試, 因?yàn)闆]有這些測試, 只是看這些枯燥的代碼是記不住這些繁瑣的流程的.

消息過程非常重要, 可以說每一個(gè)語言的消息流程設(shè)計(jì)都是核心中的核心, 有興趣的小伙伴探索一下吧.

1

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

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

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