消息转发引入
instrumentObjcMessageSends方法的使用
在上一篇文章中我们介绍了通过方法决议的方式来处理未实现的方法崩溃问题;但显然这种一刀切的方式并不可取;那么除此之外,我们还有其他方法来处理么?
我们分析通过sel
寻找imp
的过程发现,最终还调用了log_and_fill_cache
方法:
里边的logMessageSend
方法会记录方法调用的日志流程,implementer
是Class
可以确定是存在的,那么只需要objcMsgLogEnabled
的值为YES
,那么就能够记录方法调用的日志信息了:
日志信息存储路径为/tmp/msgSends-%d
,那么如果给objcMsgLogEnabled
赋值为true
呢?在源码中搜索objcMsgLogEnabled
:
发现在方法instrumentObjcMessageSends
中会给objcMsgLogEnabled
赋值,那么通过调用此方法,就可以输入方法调用的日志信息了。
我们先来看一下没有实现的talk
方法,在执行过程中的日志输出:
工程必须是个mac工程,比如
Command Line Tool
工程
运行之后,最终生成日志文件:
我们打开日志文件,日志如下:
刚才我们打印的是对象方法的调用,接下来我们看一下类方法的调用:
生成文件如下:
可以看出类方法
比对象方法
多调用了resolveClassMethod
,我们主要研究红框内的几个方法即可,其他的都是objc
的底层调用的输出内容;resolveInstanceMethod
和resolveClassMethod
方法在之前的文章中我们已经介绍过了,也知道了其调用两次的原因,那么forwardingTargetForSelector
,methodSignatureForSelector
和doesNotRecognizeSelector
是什么呢?这就是我们接下来要了解的消息转发
流程
消息转发
快速转发 forwardingTargetForSelector
接下来,我们以对象方法
的调用来分析消息转发
的流程;在之前的文件中我们看到了了forwardingTargetForSelector
方法的调用,那么这个方法是做什么的呢?
Returns the object to which unrecognized messages should first be directed.
大意是返回这个消息(unrecognized messages)应该首先指向的对象,也就是我们需要将这个无法识别的消息重定向
给另一个对象,然后看这个对象能否调用;我们现在Teacher
类中添加forwardingTargetForSelector
的默认实现:
可以看到,虽然依然没有解决崩溃问题,但是forwardingTargetForSelector
方法被成功调用,那么接下来在forwardingTargetForSelector
中把方法重定向给其他对象呢?
新建一个Student
类,在Student
中实现talk
方法,注意并不继承Person
:
@interface Student : NSObject
@end
@implementation Student
- (void)talk {
NSLog(@"%s", __func__);
}
@end
然后在forwardingTargetForSelector
中,将其重定向给Student
类的对象:
可以看到调用结果:Student
类中的方法talk
被调用,而且Student
与Person
和Teacher
并无任何继承上的联系;
这是一个快速转发
的流程;
慢速转发 methodSignatureForSelector
那么如果Student
也没有实现talk
方法结果会如何呢?根据我们在日志文件中打印的方法执行流程可以知道,如果student
也没有实现talk
,那么必然会继续报错,执行methodSignatureForSelector
方法,那么这个方法是什么意思呢?
Returns an NSMethodSignature object that contains a description of the method identified by a given selector.
Related Documentation
- forwardInvocation:
Overridden by subclasses to forward messages to other objects.
这个方法将会返回一个NSMethodSignature
对象,其包含了被调用的方法的描述信息,也就是签名;并且,他需要与forwardInvocation:
方法一起使用:
可以看到已经被methodSignatureForSelector
拦截到,接下来补全forwardInvocation:
方法的实现,forwardInvocation:
用来解读签名,与methodSignatureForSelector
一起使用:
v@:
解读:
v
表示void
类型,表示talk
的返回值@
表示调用者类型self
,即id
类型:
表示SEL
自始至终talk
没有被实现,如果需要处理可以在forwardInvocation
方法中获取到进行处理,否则将会方法将会流失掉;
hopper反汇编
接下来我们使用hopper
工具来通过反汇编来分析消息转发
;
运行原来的代码,在项目崩溃时,使用bt
指令来查看堆栈信息:
在堆栈中明确看到之前调用日志中的方法doesNotRecognizeSelector
,可以确定doesNotRecognizeSelector
在CoreFoundation
框架中,并且是___forwarding___
和_CF_forwarding_prep_0
触发的,那么我们尝试在CoreFoundation
的源码中分析
可是,不管是forwarding
还是forwarding_prep_0
,在源码中都没有找到任何蛛丝马迹,看来源码分析无法走通,Apple
并未开源这部分代码;
那么我们可以直接去看CoreFoundation
的可执行文件进行反汇编分析:
CoreFoundation
完整路径如下:
/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation
接下来使用hopper
来进行反汇编分析,将可执行文件使用hopper
打开,选择x86(64 bits)
架构:
从堆栈信息中我们可以明确是forwarding_prep_0
触发了forwarding
,那么我们以forwarding_prep_0
为切入口
进行分析:
我们以伪代码
的方式来分析其流程,完整流程如下:
针对汇编分析,流程如下