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

一、概述

上一篇文章分析到了 方法動態(tài)決議缅叠,當方法動態(tài)決議找不到imp的時候就來到了消息轉(zhuǎn)發(fā)舔哪,這篇文章將詳細分析消息轉(zhuǎn)發(fā)锻离。

1.1 instrumentObjcMessageSends 分析思路

在方法動態(tài)決議找不到imp的時候上篇文章定義了一個函數(shù)instrumentObjcMessageSends打印了后續(xù)方法的調(diào)用日志:

extern void instrumentObjcMessageSends(BOOL flag);

那么這個函數(shù)怎么來的呢铺峭?
在找到imp的時候imp會調(diào)用log_and_fill_cache插入緩存:

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
    cls->cache.insert(sel, imp, receiver);
}

當在osx下會調(diào)用logMessageSend打印日志:

image.png

條件是objcMsgLogEnabled && implementer,走到這里implementer肯定是有的汽纠,那么就需要查看objcMsgLogEnabled(默認false)是怎么賦值的卫键。搜索找到了如下代碼:

extern bool objcMsgLogEnabled;
bool objcMsgLogEnabled = false;

void instrumentObjcMessageSends(BOOL flag)
{
    bool enable = flag;

    // Shortcut NOP
    if (objcMsgLogEnabled == enable)
        return;

    // If enabling, flush all method caches so we get some traces
    if (enable)
        _objc_flush_caches(Nil);

    // Sync our log file
    if (objcMsgLogFD != -1)
        fsync (objcMsgLogFD);
   //賦值
    objcMsgLogEnabled = enable;
}

所以,也只有instrumentObjcMessageSends能決定objcMsgLogEnabled的取值虱朵,所以把這個方法暴露出去莉炉,也就能監(jiān)聽方法調(diào)用生成日志了。

就得到了方法動態(tài)決議找不到imp的后續(xù)流程了:

forwardingTargetForSelector:
methodSignatureForSelector:
resolveInstanceMethod:
doesNotRecognizeSelector:

二碴犬、消息快速轉(zhuǎn)發(fā) forwardingTargetForSelector

搜索官方文檔有如下定義:

- (id)forwardingTargetForSelector:(SEL)aSelector;
+ (id)forwardingTargetForSelector:(SEL)aSelector;

這個方法讓對象有機會重定向發(fā)送給它的未知消息呢袱,比常規(guī)消息轉(zhuǎn)發(fā)快一個數(shù)量級。分別對應類方法和實例方法翅敌。
HPObject調(diào)用沒有實現(xiàn)的方法instanceMethod,在進行緩存查找惕蹄,慢速查找蚯涮,方法動態(tài)決議后會進入消息快速轉(zhuǎn)發(fā)forwardingTargetForSelector方法中,HPSubObject如果實現(xiàn)了instanceMethod方法卖陵,則可以直接交給HPSubObject處理:

//自己解決不了遭顶,找一個相同類型的方法
- (id)forwardingTargetForSelector:(SEL)aSelector {
    NSLog(@"%s - %@",__func__,NSStringFromSelector(aSelector));
    if (aSelector == @selector(instanceMethod)) {
        return [HPSubObject alloc];
    }
    return [super forwardingTargetForSelector:aSelector];
}

這個時候調(diào)用HPObject調(diào)用instanceMethod最終會調(diào)用到HPSubObjectinstanceMethod方法。

forwardingTargetForSelector可以對于自己沒有實現(xiàn)的方法直接重定向泪蔫,沒有方法動態(tài)決議那么麻煩棒旗。如果需要對參數(shù)或返回值進行操作,那么這個方法就不適用了。

  • forwardingTargetForSelector有機會重定向發(fā)送給它的未知消息铣揉。
  • 比慢速轉(zhuǎn)發(fā)快一個數(shù)量級饶深。
  • 不能對參數(shù)和返回值進行操作。
  • 返回值為備用消息接收者逛拱。
  • 返回值不能為nil或者自己敌厘,否則進入下一個流程。
  • 在非根類中實現(xiàn)朽合,沒有自己的特定返回值則需要調(diào)用super俱两。

三、消息慢速轉(zhuǎn)發(fā) methodSignatureForSelector

如果上面的快速轉(zhuǎn)發(fā)流程仍然沒有解決問題曹步,則會進入消息慢速轉(zhuǎn)發(fā)宪彩,methodSignatureForSelector定義如下:

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;
+ (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;

這個方法對于給定的sel返回方法的簽名對象NSMethodSignature。在官方文檔中已經(jīng)說明這個方法伴隨著forwardInvocation一起使用:

- (void)forwardInvocation:(NSInvocation *)anInvocation;
+ (void)forwardInvocation:(NSInvocation *)anInvocation;

簡單打印下日志:

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    NSLog(@"%s - %@",__func__,NSStringFromSelector(aSelector));
    return [super methodSignatureForSelector:aSelector];
}

發(fā)現(xiàn)確實能夠進入該方法的調(diào)用讲婚,但是仍然沒有解決問題尿孔,上面已經(jīng)說過了真正解決問題是靠forwardInvocation,他們兩個必須成對出現(xiàn)磺樱。修改實現(xiàn)如下:

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    NSLog(@"%s - %@",__func__,NSStringFromSelector(aSelector));
    return [super methodSignatureForSelector:aSelector];
}

- (void)forwardInvocation:(NSInvocation *)anInvocation {  
}

運行后仍然報錯是因為沒有返回簽名信息纳猫。繼續(xù)修改:

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

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

簽名信息可以任意給,不一定需要與給定的sel進行匹配竹捉,提供可用的方法簽名就可以芜辕。
輸出:

-[HPObject methodSignatureForSelector:] - instanceMethod
-[HPObject forwardInvocation:] - <HPObject: 0x1014430c0> - instanceMethod

雖然沒有對這個消息進行操作,但是接收消息已經(jīng)不報錯了块差。所有消息對于系統(tǒng)來說都是事務侵续,可處理可不處理。anInvocation會被保存憨闰,當需要的時候會進行處理状蜗。

如果要處理呢?可以修改如下:

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

- (void)forwardInvocation:(NSInvocation *)anInvocation {
    NSLog(@"%s - %@ - %@",__func__,anInvocation.target,NSStringFromSelector(anInvocation.selector));
//    anInvocation.target = [HPSubObject alloc];
//    [anInvocation invoke];//執(zhí)行
    HPSubObject *subObject = [HPSubObject alloc];
    if ([self respondsToSelector:anInvocation.selector]) { //自己能夠響應
        [anInvocation invoke];
    } else if ([subObject respondsToSelector:anInvocation.selector]){
        [anInvocation invokeWithTarget:subObject];
    } else {
        //可以在這里上報錯誤等鹉动。
        NSLog(@"%s - %@",__func__,NSStringFromSelector(anInvocation.selector));
        [super forwardInvocation:anInvocation];
    }
}

根據(jù)自身業(yè)務和需求靈活處理轧坎。

慢速轉(zhuǎn)發(fā)相對于快速轉(zhuǎn)發(fā)給了很大的靈活性。如果提煉出來在NSObject分類中實現(xiàn)泽示,對于OC方法找不到的崩潰都能避免掉缸血。這個只是假象的消失,造成了很多資源的浪費械筛。進入到這里必然經(jīng)歷了很多流程捎泻。

doesNotRecognizeSelector
當慢速轉(zhuǎn)發(fā)后仍然沒有解決問題的時候會進入doesNotRecognizeSelector

- (void)doesNotRecognizeSelector:(SEL)aSelector;
+ (void)doesNotRecognizeSelector:(SEL)aSelector;

那如果重寫這個方法能不能處理錯誤呢?

If you override this method, you must call super or raise an invalidArgumentException exception at the end of your implementation. In other words, this method must not return normally; it must always result in an exception being thrown.

這個方法只是讓異陈裼矗可控笆豁,拿到錯誤信息而已。并不能處理錯誤。

  • methodSignatureForSelector對于給定的sel返回簽名對象NSMethodSignature闯狱。
  • methodSignatureForSelector必須與forwardInvocation成對出現(xiàn)一起使用煞赢。
  • methodSignatureForSelector要處理未知消息必須給定簽名信息,簽名信息只要是可用的即可扩氢。
  • forwardInvocation可以不實現(xiàn)具體內(nèi)容耕驰,如果要處理需要判斷后invoke
  • 慢速轉(zhuǎn)發(fā)相比快速轉(zhuǎn)發(fā)提供了很大的靈活性录豺,但是會造成資源浪費朦肘,能進入這里證明經(jīng)歷了很多流程。
  • doesNotRecognizeSelector并不能解決問題双饥,這個方法只是能拿到錯誤信息媒抠,讓異常可控咏花。

四趴生、反匯編分析消息轉(zhuǎn)發(fā)

上面的快速和慢速消息轉(zhuǎn)發(fā)都是基于官方文檔進行的分析,如果沒有資料或者說不熟悉這塊應該怎么入手呢昏翰?
在調(diào)用crash后直接bt

image.png

可以看到CoreFoundation框架直接調(diào)用了___forwarding_____forwarding_prep_0___后執(zhí)行了doesNotRecognizeSelector苍匆。
直接打開opensource搜索CoreFoundation,很遺憾沒有搜到棚菊。在網(wǎng)址后面拼CF直接打開CoreFoundation源碼地址(這是一個隱藏的路徑)浸踩。
image.png

下載最新的版本打開搜索___forwarding_____forwarding_prep_0___并沒有相關內(nèi)容。(蘋果沒有開源這一部分)统求。

4.1 反匯編分析

4.1.1 CoreFoundation 提取

既然蘋果沒有開源检碗,那就只剩一個辦法了,反匯編分析CoreFoundation動態(tài)庫码邻。新版本的macOS對應的CoreFoundation蘋果對齊進行了隱藏折剃。那么還有兩個方法獲取CoreFoundation動態(tài)庫:

  1. 直接新建一個iOS工程模擬器運行起來然后image list找到CoreFoundation的路徑拷貝一份:

    image.png

  2. 使用越獄手機拷貝系統(tǒng)的動態(tài)庫:

scp -r -P 12345  root@localhost:/System/Library/Caches/com.apple.dyld  ./Framework

由于系統(tǒng)的動態(tài)庫是一個庫,所以直接拷貝整個dyld_shared_cache_arm64(大小2G多)像屋。

4.1.2 Hopper 分析 CoreFoundation 庫

使用Hopper或者IDA打開CoreFoundation/dyld_shared_cache_arm64動態(tài)庫分析反匯編代碼怕犁。

如果是分析dyld_shared_cache_arm64,打開dyld_shared_cache_arm64的時候搜索選擇CoreFoundation即可:

image.png

當然也可以選擇使用dsc_extractor進行拆分這個dsc文件己莺。

./dsc_extractor path/to/dyld_shared_cache_arm64  outputdir

這樣就在拆分的庫中能找到CoreFoundation了奏甫。

4.1.3 forwarding_prep_0偽代碼分析

Hopper分析完畢后直接搜索forwarding_prep_0查看反匯編偽代碼:

int ___forwarding_prep_0___(int arg0, int arg1, int arg2, int arg3, int arg4, int arg5) {
    //……
    rax = ____forwarding___(&stack[0], 0x0);
    if (rax != 0x0) {
            rax = *rax;
    }
    else {
          //arg0,arg1
            rax = objc_msgSend(stack[0], stack[8]);
    }
    return rax;
}
  • 可以看到內(nèi)部是對___forwarding___的調(diào)用。
  • ____forwarding___返回值不存在的時候調(diào)用的是objc_msgSend參數(shù)是arg0
    arg1篇恒。

4.1.4 __forwarding__偽代碼分析

點擊進去查看___forwarding___的實現(xiàn):

int ____forwarding___(int arg0, int arg1, int arg2, int arg3, int arg4, int arg5) {
    r9 = arg5;
    r8 = arg4;
    rcx = arg3;
    r13 = arg1;
    r15 = arg0;
    rax = COND_BYTE_SET(NE);
    if (arg1 != 0x0) {
            r12 = *_objc_msgSend_stret;
    }
    else {
            r12 = *_objc_msgSend;
    }
    rbx = *(r15 + rax * 0x8);
    rsi = *(r15 + rax * 0x8 + 0x8);
    var_140 = rax * 0x8;
    if (rbx >= 0x0) goto loc_115af7;

loc_115ac0:
    //target pointer處理
    rax = *_objc_debug_taggedpointer_obfuscator;
    rax = *rax;
    rcx = (rax ^ rbx) >> 0x3c & 0x7;
    rax = ((rax ^ rbx) >> 0x34 & 0xff) + 0x8;
    if (rcx != 0x7) {
            rax = rcx;
    }
    if (rax == 0x0) goto loc_115ea6;

loc_115af7:
    var_150 = r12;
    var_138 = rsi;
    var_148 = r15;
    rax = object_getClass(rbx);
    r15 = rax;
    r12 = class_getName(rax);
    //是否能響應 forwardingTargetForSelector,不能響應跳轉(zhuǎn) loc_115bab 否則繼續(xù)執(zhí)行  也就是forwardingTargetForSelector方法返回nil或者自身
    if (class_respondsToSelector(r15, @selector(forwardingTargetForSelector:)) == 0x0) goto loc_115bab;

loc_115b38:
    //rax返回值
    rax = [rbx forwardingTargetForSelector:var_138];
    //返回值是否存在凶杖,返回值是否等于自己 是則跳轉(zhuǎn) loc_115bab
    if ((rax == 0x0) || (rax == rbx)) goto loc_115bab;

loc_115b55:
    if (rax >= 0x0) goto loc_115b91;

loc_115b5a:
    rcx = *_objc_debug_taggedpointer_obfuscator;
    rcx = *rcx;
    rdx = (rcx ^ rax) >> 0x3c & 0x7;
    rcx = ((rcx ^ rax) >> 0x34 & 0xff) + 0x8;
    if (rdx != 0x7) {
            rcx = rdx;
    }
    if (rcx == 0x0) goto loc_115e95;

loc_115b91:
    *(var_148 + var_140) = rax;
    r15 = 0x0;
    goto loc_115ef1;

loc_115ef1:
    if (**___stack_chk_guard == **___stack_chk_guard) {
            rax = r15;
    }
    else {
            rax = __stack_chk_fail();
    }
    //返回 forwardingTargetForSelector 為消息的接收者
    return rax;

loc_115e95:
    rbx = rax;
    r15 = var_148;
    r12 = var_150;
    goto loc_115ea6;

loc_115ea6:
    if (dyld_program_sdk_at_least(0x7e30901ffffffff) != 0x0) goto loc_116040;

loc_115ebd:
    r14 = _getAtomTarget(rbx);
    *(r15 + var_140) = r14;
    ___invoking___(r12, r15, r15, 0x400, 0x0, r9, var_150, var_148, var_140, var_138, var_130, stack[-304], stack[-296], stack[-288], stack[-280], stack[-272], stack[-264], stack[-256], stack[-248], stack[-240]);
    if (*r15 == r14) {
            *r15 = rbx;
    }
    goto loc_115ef1;

loc_116040:
    ____forwarding___.cold.1();
    rax = objc_opt_class(@class(NSInvocation));
    *____forwarding___.invClass = rax;
    rax = class_getInstanceSize(rax);
    *____forwarding___.invClassSize = rax;
    return rax;

loc_115bab:
    var_140 = rbx;
    //是否僵尸對象
    if (strncmp(r12, "_NSZombie_", 0xa) == 0x0) goto loc_115f30;

loc_115bce:
    r14 = var_140; 
    //是否能夠響應 methodSignatureForSelector
    if (class_respondsToSelector(r15, @selector(methodSignatureForSelector:)) == 0x0) goto loc_115f46;

loc_115bef:
    rbx = var_138;
    //調(diào)用
    rax = [r14 methodSignatureForSelector:rbx];
    if (rax == 0x0) goto loc_115fc1;

loc_115c0e:
    r15 = rax;
    rax = [rax _frameDescriptor];
    r12 = rax;
    if (((*(int16_t *)(*rax + 0x22) & 0xffff) >> 0x6 & 0x1) != r13) {
            rax = sel_getName(rbx);
            rcx = "";
            if ((*(int16_t *)(*r12 + 0x22) & 0xffff & 0x40) == 0x0) {
                    rcx = " not";
            }
            r8 = "";
            if (r13 == 0x0) {
                    r8 = " not";
            }
            _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.", rax, rcx, r8, r9, var_150);
    }
    //是否能夠響應_forwardStackInvocation
    if (class_respondsToSelector(object_getClass(r14), @selector(_forwardStackInvocation:)) == 0x0) goto loc_115d61;

loc_115c9a:
    if (*____forwarding___.onceToken != 0xffffffffffffffff) {
            dispatch_once(____forwarding___.onceToken, ^ {/* block implemented at ______forwarding____block_invoke */ } });
    }
    [NSInvocation requiredStackSizeForSignature:r15];
    var_138 = r15;
    rdx = *____forwarding___.invClassSize;
    r13 = &var_150 - (rdx + 0xf & 0xfffffffffffffff0);
    memset(r13, 0x0, rdx);
    objc_constructInstance(*____forwarding___.invClass, r13);
    var_150 = rax;
    r15 = var_138;
    [r13 _initWithMethodSignature:var_138 frame:var_148 buffer:&stack[-8] - (0xf + rax & 0xfffffffffffffff0) size:rax];
    [var_140 _forwardStackInvocation:r13];
    rbx = 0x1;
    goto loc_115dce;

loc_115dce:
    if (*(int8_t *)(r13 + 0x34) != 0x0) {
            rax = *r12;
            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 = [r15 methodReturnType];
    r14 = rax;
    rax = *(int8_t *)rax;
    if ((rax != 0x76) && (((rax != 0x56) || (*(int8_t *)(r14 + 0x1) != 0x76)))) {
            r15 = *(r13 + 0x10);
            if (rbx != 0x0) {
                    r15 = [[NSData dataWithBytes:r15 length:var_150] bytes];
                    [r13 release];
                    rax = *(int8_t *)r14;
            }
            if (rax == 0x44) {
                    asm { fld        tword [r15] };
            }
    }
    else {
            r15 = ____forwarding___.placeholder;
            if (rbx != 0x0) {
                    r15 = ____forwarding___.placeholder;
                    [r13 release];
            }
    }
    goto loc_115ef1;

loc_115d61:
    var_138 = r12;
    r12 = r14;
    //forwardInvocation的判斷胁艰,如果沒有實現(xiàn)直接跳轉(zhuǎn)loc_115f8e
    if (class_respondsToSelector(object_getClass(r14), @selector(forwardInvocation:)) == 0x0) goto loc_115f8e;

loc_115d8d:
    rax = [NSInvocation _invocationWithMethodSignature:r15 frame:var_148];
    r13 = rax;
    [r12 forwardInvocation:rax];
    var_150 = 0x0;
    rbx = 0x0;
    r12 = var_138;
    goto loc_115dce;

loc_115f8e:
    //錯誤日志
    r14 = @selector(forwardInvocation:);
    ____forwarding___.cold.4(&var_130, r12);
    rcx = r14;
    _CFLog(0x4, @"*** NSForwarding: warning: object %p of class '%s' does not implement methodSignatureForSelector: -- trouble ahead", var_140, rcx, r8, r9, var_150);
    goto loc_115fba;

loc_115fba:
    rbx = var_138;
    goto loc_115fc1;

loc_115fc1:
    rax = sel_getName(rbx);
    r14 = rax;
    rax = sel_getUid(rax);
    if (rax != rbx) {
            rcx = r14;
            r8 = rax;
            _CFLog(0x4, @"*** NSForwarding: warning: selector (%p) for message '%s' does not match selector known to Objective C runtime (%p)-- abort", var_138, rcx, r8, r9, var_150);
    }
    if (class_respondsToSelector(object_getClass(var_140), @selector(doesNotRecognizeSelector:)) == 0x0) goto loc_116034;

loc_11601b:
    [var_140 doesNotRecognizeSelector:rdx];
    asm { ud2 };
    rax = loc_116034(rdi, rsi, rdx, rcx, r8, r9);
    return rax;

loc_116034:
    ____forwarding___.cold.3(var_140);
    goto loc_116040;

loc_115f46:
    rbx = class_getSuperclass(r15);
    r14 = object_getClassName(r14);
    if (rbx == 0x0) {
            rax = object_getClassName(var_140);
            rcx = r14;
            r8 = rax;
            _CFLog(0x4, @"*** NSForwarding: warning: object %p of class '%s' does not implement methodSignatureForSelector: -- did you forget to declare the superclass of '%s'?", var_140, rcx, r8, r9, var_150);
    }
    else {
            rcx = r14;
            _CFLog(0x4, @"*** NSForwarding: warning: object %p of class '%s' does not implement methodSignatureForSelector: -- trouble ahead", var_140, rcx, r8, r9, var_150);
    }
    goto loc_115fba;

loc_115f30:
    r14 = @selector(forwardingTargetForSelector:);
    ____forwarding___.cold.2(var_140, r12, var_138, rcx, r8);
    goto loc_115f46;
}

可以看到匯編偽代碼的調(diào)用流程與看到的API調(diào)用流程差不多。

4.1.5 __forwarding__偽代碼還原

還原主要邏輯偽代碼如下:

#include <stdio.h>

@interface NSInvocation(additions)

+ (unsigned long long)requiredStackSizeForSignature:(NSMethodSignature *)signature;

-(id)_initWithMethodSignature:(id)arg1 frame:(void*)arg2 buffer:(void*)arg3 size:(unsigned long long)arg4;

+(id)_invocationWithMethodSignature:(id)arg1 frame:(void*)arg2;

@end


@interface NSObject(additions)

- (void)_forwardStackInvocation:(NSInvocation *)invocation;

@end


void forwardingTargetForSelector(Class cls, SEL sel, const char * className, id obj);
void methodSignatureForSelector(Class cls, id obj, SEL sel);
void doesNotRecognizeSelector(id obj, SEL sel);
void _forwardStackInvocation(id obj,NSMethodSignature *signature);
void forwardInvocation(id obj,NSMethodSignature *signature);

int ____forwarding___(int arg0, int arg1, int arg2, int arg3, int arg4, int arg5) {
    SEL sel = NULL;
    id obj;
    Class cls = object_getClass(obj);
    const char * className = class_getName(cls);
    forwardingTargetForSelector(cls,sel,className,obj);
    return 0;
}

void forwardingTargetForSelector(Class cls, SEL sel, const char * className, id obj) {
    //是否能響應 forwardingTargetForSelector,不能響應跳轉(zhuǎn) loc_115bab 否則繼續(xù)執(zhí)行  也就是forwardingTargetForSelector方法返回nil或者自身
    if (class_respondsToSelector(cls, @selector(forwardingTargetForSelector:))) {
        id obj = [cls forwardingTargetForSelector:sel];
        if ((obj == nil) || (obj == cls)) {
            methodSignatureForSelector(cls,obj,sel);
        } else if (obj >= 0x0) {
            //返回 forwardingTargetForSelector 備用消息接收者
//            return obj;
        } else {
            //taggedpointer 處理
            //返回NSInvocation size數(shù)據(jù)
        }
    } else {
        //是否僵尸對象
        if (strncmp(className, "_NSZombie_", 0xa)) {
            methodSignatureForSelector(cls,obj,sel);
        } else {
            SEL currentSel = @selector(forwardingTargetForSelector:);
            doesNotRecognizeSelector(obj,currentSel);
        }
    }
}


void methodSignatureForSelector(Class cls, id obj, SEL sel) {
    if (class_respondsToSelector(cls, @selector(methodSignatureForSelector:))) {
        NSMethodSignature *signature = [obj methodSignatureForSelector:sel];
        if (signature) {
            _forwardStackInvocation(obj,signature);
        } else {
            doesNotRecognizeSelector(obj,sel);
        }
    } else {
        doesNotRecognizeSelector(obj,sel);
    }
}

void _forwardStackInvocation(id obj,NSMethodSignature *signature) {
    //是否能夠響應_forwardStackInvocation
    if (class_respondsToSelector(object_getClass(obj), @selector(_forwardStackInvocation:))) {
        //執(zhí)行dispatch_once相關邏輯
        [NSInvocation requiredStackSizeForSignature:signature];
        void *bytes;
//        objc_constructInstance([NSInvocation class], bytes);
        NSInvocation *invocation = [invocation _initWithMethodSignature:signature frame:NULL buffer:NULL size:bytes];
        [obj _forwardStackInvocation:invocation];
        const char * type = [signature methodReturnType];
        //返回signature
    } else {
        forwardInvocation(obj,signature);
    }
}

void forwardInvocation(id obj,NSMethodSignature *signature) {
    //forwardInvocation的判斷腾么,如果沒有實現(xiàn)直接跳轉(zhuǎn)loc_115f8e
    if (class_respondsToSelector(object_getClass(obj), @selector(forwardInvocation:))) {
        NSInvocation *invocation = [NSInvocation _invocationWithMethodSignature:signature frame:NULL];
        [obj forwardInvocation:invocation];
        const char * type = [signature methodReturnType];
        //返回signature
    } else {
        SEL sel = @selector(forwardInvocation:);
        doesNotRecognizeSelector(obj,sel);
    }
}

void doesNotRecognizeSelector(id obj, SEL sel) {
    if (class_respondsToSelector(object_getClass(obj), @selector(doesNotRecognizeSelector:))) {
        [obj doesNotRecognizeSelector:sel];
        /*
         ____forwarding___.cold.1();
         rax = objc_opt_class(@class(NSInvocation));
         *____forwarding___.invClass = rax;
         rax = class_getInstanceSize(rax);
         *____forwarding___.invClassSize = rax;
         return rax;
         */
    } else {
        /*
         ____forwarding___.cold.1();
         rax = objc_opt_class(@class(NSInvocation));
         *____forwarding___.invClass = rax;
         rax = class_getInstanceSize(rax);
         *____forwarding___.invClassSize = rax;
         return rax;
         */
    }
}

為了方便分析我這里class-dumpCoreFoundation頭文件奈梳。手機端使用cycript進入SpringBoard應用,然后classdumpdyld導出CoreFoudation的頭文件解虱,最后拷貝到電腦端攘须,具體操作如下:

cycript -p SpringBoard
@import net.limneos.classdumpdyld;
classdumpdyld.dumpBundle([NSBundle > bundleWithIdentifier:@"com.apple.CoreFoudation"]);
//輸出導出頭文件路徑
@"Wrote all headers to /tmp/CoreFoundation"
//拷貝到電腦的相應目錄
scp -r  -P 12345  root@localhost:/tmp/CoreFoundation/  ./CoreFoundation_Headers/

詳情可以看我之前的文章class-dump
??在導出的NSObject頭文件中并沒有發(fā)現(xiàn)_forwardStackInvocation方法。目前并不清楚這個方法是在哪里定義的殴泰。

偽代碼流程圖如下:


消息轉(zhuǎn)發(fā)反匯編流程

反匯編流程與根據(jù)API分析的流程差不多于宙。

  • forwardingTargetForSelector快速轉(zhuǎn)發(fā)會對返回值會進行判斷,如果是返回的自身或者nil直接進入下一流程(慢速轉(zhuǎn)發(fā))悍汛。
  • 如果返回taggedpointer有單獨的處理捞魁。
  • methodSignatureForSelector慢速轉(zhuǎn)發(fā)會先判斷有沒有實現(xiàn)_forwardStackInvocation(私有方法)。實現(xiàn)_forwardStackInvocation后不會再進入forwardInvocation流程离咐,相當于_forwardStackInvocation是一個私有的前置條件谱俭。
  • methodSignatureForSelector如果沒有返回簽名信息不會繼續(xù)進行下面的流程。
  • forwardInvocation沒有實現(xiàn)就直接走到doesNotRecognizeSelector流程了宵蛀。

4.2 流程分析

上篇文章分析resolveInstanceMethod在消息轉(zhuǎn)發(fā)后還會調(diào)用一次resolveInstanceMethod(在日志文件中看到是在doesNotRecognizeSelector之前昆著,methodSignatureForSelector之后)。那么實現(xiàn)對應的方法做下驗證:

HPObject resolveInstanceMethod: HPObject-0x100008290-instanceMethod
-[HPObject forwardingTargetForSelector:] - instanceMethod
-[HPObject methodSignatureForSelector:] - instanceMethod
HPObject resolveInstanceMethod: HPObject-0x100008290-instanceMethod
-[HPObject doesNotRecognizeSelector:] - instanceMethod

證實是在methodSignatureForSelector之后术陶,doesNotRecognizeSelector之前有一次進行了方法動態(tài)決議凑懂。那么為什么要這么處理呢?因為消息轉(zhuǎn)發(fā)的過程中可能已經(jīng)加入了對應的sel-imp瞳别,所以再給一次機會進行方法動態(tài)決議征候。這次決議后不會再進行消息轉(zhuǎn)發(fā)。

但是在反匯編分析中并沒有明確的再次進行動態(tài)方法決議的邏輯祟敛。

4.2.1 反匯編以及源碼探究

那么在第二次調(diào)用resolveInstanceMethod前打斷點查看下堆棧信息
macOS堆棧如下:

* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 5.1
    frame #0: 0x0000000100300f53 libobjc.A.dylib`resolveMethod_locked(inst=0x0000000000000000, sel="instanceMethod", cls=HPObject, behavior=0) at objc-runtime-new.mm:6339:13
    frame #1: 0x00000001002ffbd5 libobjc.A.dylib`lookUpImpOrForward(inst=0x0000000000000000, sel="instanceMethod", cls=HPObject, behavior=0) at objc-runtime-new.mm:6601:16
    frame #2: 0x00000001002d6df9 libobjc.A.dylib`class_getInstanceMethod(cls=HPObject, sel="instanceMethod") at objc-runtime-new.mm:6210:5
  * frame #3: 0x00007fff2e33fc68 CoreFoundation`__methodDescriptionForSelector + 282
    frame #4: 0x00007fff2e35b57c CoreFoundation`-[NSObject(NSObject) methodSignatureForSelector:] + 38
    frame #5: 0x0000000100003a21 HPObjcTest`-[HPObject methodSignatureForSelector:](self=0x0000000100706a30, _cmd="methodSignatureForSelector:", aSelector="instanceMethod") at HPObject.m:29:12 [opt]
    frame #6: 0x00007fff2e327fc0 CoreFoundation`___forwarding___ + 408
    frame #7: 0x00007fff2e327d98 CoreFoundation`__forwarding_prep_0___ + 120
    frame #8: 0x0000000100003c79 HPObjcTest`main + 153
    frame #9: 0x00007fff683fecc9 libdyld.dylib`start + 1
    frame #10: 0x00007fff683fecc9 libdyld.dylib`start + 1

可以看到methodSignatureForSelector調(diào)用后進入了__methodDescriptionForSelector隨后調(diào)用了class_getInstanceMethod疤坝。查看匯編確實在__methodDescriptionForSelector中調(diào)用了class_getInstanceMethod

image.png

那么系統(tǒng)是如何從methodSignatureForSelector調(diào)用到__methodDescriptionForSelector的?
當前的methodSignatureForSelector的實現(xiàn)是:

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    NSLog(@"%s - %@",__func__,NSStringFromSelector(aSelector));
    return [super methodSignatureForSelector:aSelector];
}

如果改為返回nil呢馆铁?

HPObject resolveInstanceMethod: HPObject-0x100008288-instanceMethod
-[HPObject forwardingTargetForSelector:] - instanceMethod
-[HPObject methodSignatureForSelector:] - instanceMethod
-[HPObject doesNotRecognizeSelector:] - instanceMethod

這個時候發(fā)現(xiàn)沒有第二次調(diào)用了跑揉,那也就是說核心邏輯在[super methodSignatureForSelector:aSelector]的實現(xiàn)中。
查看源碼:

// Replaced by CF (returns an NSMethodSignature)
+ (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
    _objc_fatal("+[NSObject methodSignatureForSelector:] "
                "not available without CoreFoundation");
}

// Replaced by CF (returns an NSMethodSignature)
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
    _objc_fatal("-[NSObject methodSignatureForSelector:] "
                "not available without CoreFoundation");
}

注釋說的已經(jīng)很明顯了實現(xiàn)在CoreFoundation中埠巨,直接搜索methodSignatureForSelector的反匯編實現(xiàn):

/* @class NSObject */
-(void *)methodSignatureForSelector:(void *)arg2 {
    rdx = arg2;
    if ((rdx != 0x0) && (___methodDescriptionForSelector(objc_opt_class(), rdx) != 0x0)) {
            rax = [NSMethodSignature signatureWithObjCTypes:rdx];
    }
    else {
            rax = 0x0;
    }
    return rax;
}
  • sel不為nil的時候會調(diào)用___methodDescriptionForSelector历谍。這樣就串聯(lián)起來了。

class_getInstanceMethod的實現(xiàn)如下:

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

4.2.2 斷點調(diào)試驗證

既然上面已經(jīng)清楚了resolveInstanceMethod第二次調(diào)用是methodSignatureForSelector之后調(diào)用的辣垒,那么不妨打個符號斷點跟蹤下methodSignatureForSelector:望侈。

image.png

顯然只需要關心調(diào)用的函數(shù)以及跳轉(zhuǎn)邏輯。

跟進去__methodDescriptionForSelector

image.png

繼續(xù)進入class_getInstanceMethod

image.png

繼續(xù)跳轉(zhuǎn):


image.png

這樣就定位到了lookUpImpOrForward勋桶,繼續(xù)進入lookUpImpOrForward脱衙,最終會定位到resolveInstanceMethod

image.png

這樣通過斷點也從methodSignatureForSelector定位到了resolveInstanceMethod侥猬。

結(jié)論:

  • 實例方法 - methodSignatureForSelector-> ___methodDescriptionForSelector -> class_getInstanceMethod-> lookUpImpOrForward->resolveMethod_locked-> resolveInstanceMethod
  • 類方法 + methodSignatureForSelector -> ___methodDescriptionForSelector(傳遞的是元類) -> class_getInstanceMethod- lookUpImpOrForward->resolveMethod_locked-> resolveClassMethod捐韩。

??總結(jié):

  1. 在methodSignatureForSelector內(nèi)部調(diào)用了class_getInstanceMethod進行lookUpImpOrForward隨后進入方法動態(tài)決議退唠。這也就是class_getInstanceMethod調(diào)用第二次的來源入口。
  2. methodSignatureForSelector后第二次調(diào)用class_getInstanceMethod是為了再給一次進行消息查找和動態(tài)決議流程荤胁,因為消息轉(zhuǎn)發(fā)流程過程中有可能實現(xiàn)了對應的sel-imp瞧预。

動態(tài)方法決議以及消息轉(zhuǎn)發(fā)整個流程如下:

方法動態(tài)決議&消息轉(zhuǎn)發(fā)

五、消息發(fā)送查找總結(jié)

前面已經(jīng)通過objc_msgSend分析整個消息緩存仅政、查找垢油、決議、轉(zhuǎn)發(fā)整個流程已旧。

  • 通過CacheLookup進行消息快速查找秸苗。
    • 整個cache查找過程相當于是insert過程的逆過程,找到imp就解碼跳轉(zhuǎn)运褪,否則進入慢速查找流程惊楼。
  • 通過lookUpImpOrForward進行消息慢速查找
    • 慢速查找涉及到遞歸查找秸讹,查找過程分為二分查找/循環(huán)查找檀咙。
    • 找到imp直接跳轉(zhuǎn),否則查找父類緩存璃诀。父類緩存依然找不到則在父類方法列表中查找弧可,直到找到nil。查找到父類方法/緩存方法直接插入自己的緩存中劣欢。
  • imp找不到的時候進行方法動態(tài)決議棕诵。
    • 當快速和慢速消息查找都沒有找到imp的時候就進入了方法動態(tài)決議流程,在這個流程中主要是添加imp后再次進行快速慢速消息查找凿将。
  • 之后進入本篇的消息轉(zhuǎn)發(fā)流程校套,消息轉(zhuǎn)發(fā)分為快速以及慢速。
    • 在動態(tài)方法決議沒有返回imp的時候就進入到了消息轉(zhuǎn)發(fā)階段牧抵。
    • 快速消息轉(zhuǎn)發(fā)提供一個備用消息接收者笛匙,返回值不能為nil與自身。這個過程不能修改參數(shù)和返回值犀变。
    • 慢速消息轉(zhuǎn)發(fā)需要提供消息簽名妹孙,只要提供有效簽名就可以解決消息發(fā)送錯誤問題。同時要實現(xiàn)forwardInvocation配合處理消息获枝。
    • forwardInvocation配合處理消息蠢正,使target生效起作用。
    • 在慢速消息轉(zhuǎn)發(fā)后系統(tǒng)會再進行一次慢速消息查找流程省店。這次不會再進行消息轉(zhuǎn)發(fā)嚣崭。
    • 消息轉(zhuǎn)發(fā)仍然沒有解決問題會進入doesNotRecognizeSelector蜘拉,這個方法并不能處理錯誤,實現(xiàn)它仍然會報錯有鹿。只是能拿到錯誤信息而已。

??慢速消息轉(zhuǎn)發(fā)后系統(tǒng)仍然給了一次機會進行 慢速消息查找;迅4邪稀!(并不僅僅是動態(tài)方法決議)源梭。

整個流程如下:


objc_msgSend消息處理流程
最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末娱俺,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子废麻,更是在濱河造成了極大的恐慌荠卷,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,204評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件烛愧,死亡現(xiàn)場離奇詭異油宜,居然都是意外死亡,警方通過查閱死者的電腦和手機怜姿,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,091評論 3 395
  • 文/潘曉璐 我一進店門慎冤,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人沧卢,你說我怎么就攤上這事蚁堤。” “怎么了但狭?”我有些...
    開封第一講書人閱讀 164,548評論 0 354
  • 文/不壞的土叔 我叫張陵披诗,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么娜膘? 我笑而不...
    開封第一講書人閱讀 58,657評論 1 293
  • 正文 為了忘掉前任逊谋,我火速辦了婚禮,結(jié)果婚禮上洪鸭,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好绍刮,可當我...
    茶點故事閱讀 67,689評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著挨摸,像睡著了一般孩革。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上得运,一...
    開封第一講書人閱讀 51,554評論 1 305
  • 那天膝蜈,我揣著相機與錄音锅移,去河邊找鬼。 笑死饱搏,一個胖子當著我的面吹牛非剃,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播推沸,決...
    沈念sama閱讀 40,302評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼备绽,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了鬓催?” 一聲冷哼從身側(cè)響起肺素,我...
    開封第一講書人閱讀 39,216評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎宇驾,沒想到半個月后倍靡,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,661評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡课舍,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,851評論 3 336
  • 正文 我和宋清朗相戀三年塌西,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片筝尾。...
    茶點故事閱讀 39,977評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡雨让,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出忿等,到底是詐尸還是另有隱情栖忠,我是刑警寧澤,帶...
    沈念sama閱讀 35,697評論 5 347
  • 正文 年R本政府宣布贸街,位于F島的核電站庵寞,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏薛匪。R本人自食惡果不足惜捐川,卻給世界環(huán)境...
    茶點故事閱讀 41,306評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望逸尖。 院中可真熱鬧古沥,春花似錦、人聲如沸娇跟。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,898評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽苞俘。三九已至盹沈,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間吃谣,已是汗流浹背乞封。 一陣腳步聲響...
    開封第一講書人閱讀 33,019評論 1 270
  • 我被黑心中介騙來泰國打工做裙, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人肃晚。 一個月前我還...
    沈念sama閱讀 48,138評論 3 370
  • 正文 我出身青樓锚贱,卻偏偏與公主長得像,于是被迫代替她去往敵國和親关串。 傳聞我的和親對象是個殘疾皇子惋鸥,可洞房花燭夜當晚...
    茶點故事閱讀 44,927評論 2 355

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