msgSend底层(四)消息转发

528 阅读2分钟

前言

在上一篇动态方法决议中中,从消息慢速查找没有找到就会进入动态决议,如果动态决议没有处理,通过崩溃日志可以看到forwardingTargetForSelectormethodSignatureForSelector也就是消息查找的最后流程消息转发

消息快速转发 和 消息慢速转发

  • forwardingTargetForSelector 在源码中只有申明,没有具体的方法, 在NSObject文档中也只是说明,如果方法执行不了,可以转发给别人来处理
  • forwardingTargetForSelector后面还有个methodSignatureForSelector方法,也就是快速转发没找到,就会进入methodSignatureForSelector方法,文档上说,这个方法要返回一个方法签名NSMethodSignature,而且在消息的转发期间,必须要创建NSInvocation,也就是实现forwardInvocation方法

代码验证

@implementation ZMPerson
// 正常的消息转发
+ (BOOL)resolveInstanceMethod:(SEL)sel {
    NSString * methodName = NSStringFromSelector(sel);
    if ([methodName isEqualToString:@"sendMessage:"]) {
        return class_addMethod(self, sel, (IMP)sendMessage, "v@:@");
    }
    return NO;
}
// 快速转发
- (id)forwardingTargetForSelector:(SEL)aSelector {
    NSString * methodName = NSStringFromSelector(aSelector);
    if ([methodName isEqualToString:@"sendMessage:"]) {
        return [ZMStudent new];
    }
    return [super forwardingTargetForSelector:aSelector];
}
// 1. 方法签名
// 2. 消息转发
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    NSString * methodName = NSStringFromSelector(aSelector);
    if ([methodName isEqualToString:@"sendMessage:"]) {
        return [NSMethodSignature signatureWithObjCTypes:"v@:@"];
    }
    return [super methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation {
    SEL sel = [anInvocation selector];
    ZMStudent * tempObj = [ZMStudent new];
    if ([tempObj respondsToSelector:sel]) {
        [anInvocation invokeWithTarget:tempObj];
    }else {
        [super forwardInvocation:anInvocation];
    }
}
- (void)doesNotRecognizeSelector:(SEL)aSelector {
    NSLog(@"消息转发失败");
}

总结

  • 快速转发:调用forwardingTargetForSelector实现快速转发流程,可以返回自己想要替自己实现的对象去实现该方法,如果返回nil将进入慢速转发流程。
  • 慢速转发:慢速转发需要调用methodSignatureForSelectorforwardInvocation共同实现,methodSignatureForSelector需要返回方法签名,而forwardInvocation里面可以做一些处理,例如指定对象处理,指定方法等。如果 没有方法签名,则结束慢速查找。

消息转发图解

image.png

消息转发汇编

  • 假如这些转发流程都没有处理,运行肯定是报错的,那么怎么查看呢,我们可以用bt命令打印堆栈:

image.png 通过分析得知,在doesNotRecognizeSelector之后还走两个方法___forwarding_____forwarding_prep_0___方法,这两个方法都在CoreFoundation库中,按照以往的经验去源码中分析下作用,但是源码中并没有找到,说明CoreFoundation库并没有完全开源

反汇编 Hppper

将找到的CoreFoundation动态库拖到Hopper中 

image.png

  • 然后点击左侧label搜索__forwarding_prep_0___,主要查看伪代码

image.png

  • 我们看到__forwarding_prep_0___中调用___forwarding___后得到rax,双击进入___forwarding___方法:

image.png

  • 这里显示判断forwardingTargetForSelector有没有实现,如果没有实现就会走loc_64ad7,也就是慢速转发:

image.png

  • 在慢速转发时,先判断是不是僵尸对象_NSZombie_,再判断methodSignatureForSelector是否实现,没有实现就走loc_64e47
  • 如果methodSignatureForSelector返回值为nil,则走loc_64eac,也就是报错
  • 如果methodSignatureForSelector有返回值,则会去判断forwardStackInvocation方法是否存在,然后进行后面的处理