iOS底層探索消息轉發

上篇內容,若是方法的調用,沒有實現動態方法決議,那麼就會進行消息轉發。消費轉發分爲兩個階段分別以下:sass

快速轉發

要實現快速轉發須要在未實現實現動態方法決議的狀況下,實現下面函數:markdown

//將方法的調用轉給其餘對象(本身作不了,就交給其餘人作 )
-(id)forwardingTargetForSelector:(SEL)aSelector
+(id)forwardingTargetForSelector:(SEL)aSelector
複製代碼

代碼演示:函數

#import <Foundation/Foundation.h>

@interface ABPerson : NSObject
@end
@implementation ABPerson
- (void)doSomething
{
    NSLog(@"%s",__func__);
}
@end
@interface ABPeople : NSObject
- (void)doSomething;
@end
@implementation ABPeople

-(id)forwardingTargetForSelector:(SEL)aSelector{
     
    NSLog(@"%s-%@",__func__,NSStringFromSelector(aSelector));
    return  [ABPerson alloc];
}

@end
int main(int argc, const char * argv[]) {
    @autoreleasepool {
    
        ABPeople *p = [ABPeople new];
       
        [p doSomething];
        
        NSLog(@"Hello, World!");
    }
    return 0;
}
複製代碼

image.png ABPeople沒有實現的方法交給了ABPerson實現並調用。可是若是快速轉發也沒有實現呢?那就進行慢速轉發。post

慢速轉發

//經過方法編號,返回方法簽名
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
+ (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
複製代碼
//調用者Target,方法sel都被保存在anInvocation中
-(void)forwardInvocation:(NSInvocation *)anInvocation
+(void)forwardInvocation:(NSInvocation *)anInvocation
複製代碼

這兩個方法必須同時實現,forwardInvocation能夠實現空方法,也不會報錯。ui

代碼演示:spa

#import <Foundation/Foundation.h>

@interface ABPeople : NSObject
- (void)doSomething;
@end
@implementation ABPeople

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
    NSLog(@"%s-%@",__func__,NSStringFromSelector(aSelector));
    if (aSelector == @selector(doSomething)) {
        return [NSMethodSignature signatureWithObjCTypes:"v@:"];
    }
    return [super methodSignatureForSelector:aSelector];
}
-(void)forwardInvocation:(NSInvocation *)anInvocation
{
    NSLog(@"%@- %@",anInvocation.target,NSStringFromSelector(anInvocation.selector));
    ABPerson *p = [ABPerson new];
    if ([p respondsToSelector:anInvocation.selector]) {
        anInvocation.target = p;
        [anInvocation invoke];
    }
}

@end
int main(int argc, const char * argv[]) {
    @autoreleasepool {
    
        ABPeople *p = [ABPeople new];
       
        [p doSomething];
        
        NSLog(@"Hello, World!");
    }
    return 0;
}
複製代碼

image.png

Hopper反彙編CoreFoundation

在方法找不到,崩潰的時候,咱們經過bt命令打印調用棧:調試

image.png 在找不到方法的最後處理doesNotRecognizeSelector,執行了紅框中的東西,能看出它們來自CoreFoundationcode

CoreFoundation可執行文件拖到Hopper Disassemalber,查看反彙編僞代碼。orm

全局搜索forwarding對象

image.png ___forwarding_prep_0___調用了 ____forwarding___,查看 ____forwarding___實現:

int ____forwarding___(int arg0, int arg1) {

   省略部分僞代碼

loc_649bb:
    var_148 = r13;
    var_138 = r12;
    var_158 = rsi;
    rax = object_getClass(rbx);
    r12 = rax;
    r13 = class_getName(rax);
    //當類沒有實現forwardingTargetForSelector,就執行loc_64a67
    if (class_respondsToSelector(r12, @selector(forwardingTargetForSelector:)) == 0x0) goto loc_64a67;

loc_649fc:
    rdi = rbx;
    //當類實現了forwardingTargetForSelector,就返回rax(對象)
    rax = [rdi forwardingTargetForSelector:var_140];
    //若是rax不存在或等於原來的對象就就執行loc_64a67
    if ((rax == 0x0) || (rax == rbx)) goto loc_64a67;

省略部分僞代碼

loc_64a67:
    var_138 = rbx;
    //判斷是不是殭屍對象
    if (strncmp(r13, "_NSZombie_", 0xa) == 0x0) goto loc_64dc1;

loc_64a8a:
   //當類沒有實現methodSignatureForSelector,就執行loc_64dd7
    rax = class_respondsToSelector(r12, @selector(methodSignatureForSelector:));
    r14 = var_138;
    var_148 = r15;
    if (rax == 0x0) goto loc_64dd7;

loc_64ab2:
 //當類實現了methodSignatureForSelector,就返回rax(對象)
    rax = [r14 methodSignatureForSelector:var_140];
    rbx = var_158;
    //沒有實現就執行loc_64e3c
    if (rax == 0x0) goto loc_64e3c;
    
省略部分僞代碼

    //系統實現的方法_forwardStackInvocation,並無對外暴露
    rax = class_respondsToSelector(rax, @selector(_forwardStackInvocation:));
    var_150 = r13;
    if (rax == 0x0) goto loc_64c19;

loc_64b6c:
    if (*0x5c2700 != 0xffffffffffffffff) {
            dispatch_once(0x5c2700, ^ {/* block implemented at ______forwarding____block_invoke */ } });
    }
    //NSInvocation的一些處理
    r15 = [NSInvocation requiredStackSizeForSignature:r12];
    rsi = *0x5c26f8;
    rsp = rsp - ___chkstk_darwin(@class(NSInvocation), rsi, r12, rcx);
    r13 = &stack[-360];
    __bzero(r13, rsi);
    ___chkstk_darwin(r13, rsi, r12, rcx);
    rax = objc_constructInstance(*0x5c26f0, r13);
    var_140 = r15;
    [r13 _initWithMethodSignature:r12 frame:var_148 buffer:&stack[-360] size:r15];
    [var_138 _forwardStackInvocation:r13];
    r14 = 0x1;
    goto loc_64c76;

省略部分僞代碼

loc_64c19:
//類是否能響應forwardInvocation,不能響應就執行loc_64ec2
    if (class_respondsToSelector(object_getClass(r14), @selector(forwardInvocation:)) == 0x0) goto loc_64ec2;

loc_64c3b:
    rax = [NSInvocation _invocationWithMethodSignature:r12 frame:var_148];
    r13 = rax;
    //開始調用
    [r14 forwardInvocation:rax];
    var_140 = 0x0;
    r14 = 0x0;
    goto loc_64c76;

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

複製代碼

消息轉發流程圖

消息.png

總結

消息轉發流程(經過sel尋找imp)以下:

  1. lookUpImpOrForward慢速查找沒有找到imp
  2. resolveMethod_locked判斷當前class是否是類
  3. 是類調用resolveClassMethod執行類動態方法決議
  4. 是類調用resolveInstanceMethod執行對象動態方法決議
  5. 未實現動態方法決議或未處理成功,查看是否實現快速轉發forwardingTargetForSelector
  6. 未實現快速轉發,就查看是否實現慢速轉發:methodSignatureForSelector返回簽名,forwardInvocation調用執行
  7. 若是也爲實現慢速轉發,就調用doesNotRecognizeSelector
  8. 動態方法決議、快速轉發、慢速轉發任何一個階段成功了,就直接處理消息。

補充

當方法爲實現時,動態方法決議會打印兩遍

image.png

objc工程調試:

image.png 在此文件函數內打上斷點

image.png 在第二次即將輸出當前sel的時候,打印函數調用棧

緣由是CoreFoundation調用了__forwarding_prep_0___,通過系統處理調用了class_respondsToSelector_inst。調用流程以下:

__forwarding_prep_0___->class_respondsToSelector_inst->lookUpImpOrNilTryCache->lookUpImpOrForward

這樣作的目的是在消息轉發過程當中,若是imp被加上了,能夠再次獲得處理。

相關文章
相關標籤/搜索