打印消息转发流程
在开始正文之前,首先介绍一个工具instrumentObjcMessageSends
当打开instrumentObjcMessageSends, 那么每一个方法调用都会输出到"/tmp/msgSends-%d", (int) getpid ()这个文件中
对于后面探索消息转发流程起到重要的作用
消息转发对于未实现方法的处理
在上一章节探索了类对象方法的动态决议,但是在探索的过程中发现,当一个实例方法不存在的时候,resolveInstanceMethod:会被调用两次. 打开instrumentObjcMessageSends, 如下代码调用一个未实现的方法,看看调用过程是怎么样的
观察到调用了两次
resolveInstanceMethod:之后,又调用了两次forwardingTargetForSelector:, 此时比较奇怪,这个forwardingTargetForSelector:是什么,搜索苹果官方文档,给出的说明是
Returns the object to which unrecognized messages should first be directed.//返回未识别的消息首先应指向的对象如果一个对象实现(或继承)这个方法,并返回一个非nil(和非self)结果,则返回的对象将用作新的接收对象,消息分派将继续到该新对象。(显然,如果你从这个方法返回self,代码将陷入无限循环。)如果您在非根类中实现此方法,如果您的类对于给定的选择器没有任何返回,那么您应该返回调用super实现的结果。此方法使对象有机会在开销更大的forwardInvocation:机制接管之前,重定向发送给它的未知消息。当您只是想将消息重定向到另一个对象时,这是非常有用的,并且可以比常规转发快一个数量级。如果转发的目标是捕获NSInvocation,或者在转发期间操纵参数或返回值,那么它就没有用了。
那么forwardingTargetForSelector:给一个机会去重定向sel的receiver, 属于快速转发. 尝试修复上面的crash,新建一个MLStudent并且实现sayInstanceMethod
运行一下,没有任何crash,并且执行了MLStudent的sayInstanceMethod方法. 查看之前crash的调用过程,还发现一个方法的调用methodSignatureForSelector:,这时就进入慢速转发流程, 搜索官方文档,给出如下说明:
Returns an
NSMethodSignatureobject that contains a description of the method identified by a given selector.- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector; 该方法用于协议的实现。这个方法也用于必须创建NSInvocation对象的情况,例如在消息转发期间。如果对象维护委托或能够处理它没有直接实现的消息,则应重写此方法以返回适当的方法签名。需要跟`forwardInvocation:`搭配使用.
文档说明,我们可以返回一个NSMethodSignature包含这个sel的描述, 关于什么是方法签名,再次不做详细解释, 当我们添加如下代码到MLPerson,
控制台也没有任何关于sayInstanceMethod的调用打印。所有的方法,都统称为消息,同时也统称为事务(invocation),这个事务可做可不做,当返回了一个NSMethodSignature之后,并且空实现forwardInvocation:, 当前事务就不会执行
小结
当进入消息转发流程的过程中,如果这个sel没有任何实现,有如下方式可以避免crash
- 重写
forwardingTargetForSelector:返回一个实现这个sel的对象,进行重定向转发 - 重写
methodSignatureForSelector:, 并且重写forwardInvocation:决定要不要执行当前事务, 可以空实现,也可以按照如下方法实现
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
MLStudent *student = [MLStudent alloc];
if ([self respondsToSelector:anInvocation.selector])
{
[anInvocation invoke];
}
else if ([student respondsToSelector:anInvocation.selector])
{
[anInvocation invokeWithTarget:student];
}
else
{
[super forwardInvocation:anInvocation];
}
}
doesNotRecognizeSelector
当调用一个未实现的方法,控制台会打印出如下消息,bt打印出当前的调用堆栈
可以看到,调用
doesNotRecognizeSelector是通过___forwarding__, 但是___forwarding___这个符号是在CoreFoundation里面的, CoreFoundation源码地址, 下载到源码之后,搜索forwarding相关字段,并没有搜到任何相关的代码, 接下来我们借助一个反汇编工具Hopper, 这个工具可以通过汇编代码,反汇编出伪代码
从反汇编得到的伪代码,也解释了为什么
methodSignatureForSelector:和forwardInvocation:需要搭配使用,只有实现了methodSignatureForSelector:才会调用到forwardInvocation:
最终所有的能补救的流程都走完了,如果还是不行,就调用doesNotRecognizeSelector.
消息转发对于未实现方法的处理流程图
resolveInstanceMethod 为什么会执行两次
将断点打到resolveInstanceMethod:中, 停在第二次调用resolveInstanceMethod:, 打印出堆栈
发现第二次调用
resolveInstanceMethod: 是在消息转发的流程。 第一次调用是在消息查找慢速流程中调用的.