在上一篇文章中,我们了解了方法的慢速查找流程以及动态方法决议,在动态方法决议之后其实还不会崩溃,后面还会走到消息转发流程, 这一篇文章就来了解一下消息的转发机制。
在讲解消息转发机制之前,我们先来补充一下类方法的动态决议。
由图可以看出,对象方法直接调用了resolveInstanceMethod
, 然而类方法调用完resolveClassMethod
还有一个判断条件,如果需要查找cls类中有该方法的实现还会走一次resolveInstanceMethod
,为什么会这样呢?
这里是因为类方法在元类中是对象方法的形式存在,所以还是需要查询元类中的对象方法。但由于我们无法在元类里面进行操作,所以只能根据元类的走向,最终会来到NSObject
,可以在NSObject
里面进行resolveInstanceMethod
。
这里其实又是isa的走位图
,元类的最终指向是NSObject
,类的最终指向也是NSObject
,所以我们可以建一个NSObject
分类,将对象方法和类方法的动态决议都放在NSObject
里面,如下图:
这样的话就非常的便利了,然后再封装一个SDK,所有的方法报错都可以在这里面得到处理。
但是这样的话有可能其它类的动态方法决议写在了他自己的分类里面,会搞的很乱。 所以接下来进入我们的主角,用消息转发来处理崩溃。
消息转发
在动态方法决议完了之后还没找到imp,会来到消息转发,但是我们找了半天,也没找不到消息转发相关的源码。走在金字塔顶端的人永远是少数, 这里的话一般人又玩不了了,那么怎么办呢, 有两种方法:
1、通过instrumentObjcMessageSends
方式打印发送消息的日志
2、通过Hopper
或者IDA
反编译的方式(Hopper要钱,IDA不要钱,但要在windows下运行才稳定)
首先我们来看instrumentObjcMessageSends
从源码中我们可以看出里面有一个控制开关,根据objcMsgLogEnabled
是否打开来判断是否调用logMessageSend
打印日志,接下来我们就调用instrumentObjcMessageSends
将objcMsgLogEnabled
设置成YES
,设置成YES
之后就会来到logMessageSend
打印日志
由于instrumentObjcMessageSends
是内部的方法,在外面无法直接调用,所以我们用extern
让它被外界知道,然后将sayHello
包起来
包起来之后运行代码,运行完了之后,我们根据logMessageSend
里面的/tmp/msgSends
路径来到msgSends
,发现msgSends
下有一个 msgSends-2376
,这个里面就是打印的日志信息,我们打开来看一下
看到这里,整个崩溃前的调用流程我想大家都清楚了,我怕有的人还不清楚,我再截个图注释一下。这个地方说明一下,在慢速转发签名之后还会调用一次resolveInstanceMethod
,系统给了一次不让漂流瓶流出去的机会。所以加上签名之后的一次,resolveInstanceMethod
总共会来两次。如果在签名之前把消息处理了就只会来一次
我们先来看看消息快速转发
,官方的解释是如果这个消息没有人接收的时候,返回它的第一继承人去实现,比如我在forwardingTargetForSelector
返回[LGStudent alloc]
,LGStudent
里面定义了sayHello
,运行,结果就不报错了,这就是牛逼的消息快速转发流程
再来看看我们的消息慢速转发流程,需要注意的是,慢速转发需要返回一个消息签名,不能返回空,签名如果返回空就会报错。然后根据官方文档的解释再调用forwardInvocation
将消息抛出去给其他对象
看完官方文档之后我们用代码来进行慢速查找一下
没有崩溃,非常的完美, 那么anInvocation
究竟是什么玩意呢,我们点开来看一下
发现anInvocation
里面的有方法的信息,那么为什么没有崩溃了呢,其实就是把anInvocation
这个事务抛出去了,就跟漂流瓶一样。 同时我们还可以将这个事务保存下来,以便知道方法崩溃的信息,所以慢速转发更加灵活,更加舒服。
所以整个的消息流程就如下面这幅图:
接下来我们再通过反编译的方式来查看流程
这里需要用一个工具Hopper
,这个工具可以将我们可执行文件反汇编成伪代码、控制流程图等
我们把程序运行崩溃,然后查看堆栈信息,发现___forwarding___
来自CoreFoundation
但是我们怎么找都找不到CoreFoundation
的源码, 苹果闭源了, 所以这时候我们就要用到Hopper
通过反汇编来查看。
我们通过image list
读取镜像找到CoreFoundation
文件路径,然后找到可执行文件
找到可执行文件后,打开hopper
,选择Try the Demo
,然后将可执行文件拖入hopper进行反汇编,选择x86(64 bits)
,拖进去之后神奇的一幕就发生了,我们将CoreFoundation
可执行文件进行反汇编了,反汇编之后我们全局搜索__forwarding_prep_0___
找到之后我们点进去,点进去之后发现看不懂,看不懂就再点击上面的伪代码按钮,如下图
点完伪代码之后,我们再点击____forwarding___
,往下看, 会来到forwardingTargetForSelector:
快速转发流程
如果快速转发没有响应,或者返回值为空的话,则会跳转至loc_64a67
进入慢速转发流程
在慢速转发过程中,如果没有响应methodSignatureForSelector
,则走到loc_64dd7
直接报错。如果获取methodSignatureForSelector
的方法签名为nil,则走到loc_64e3c
里面报错。
如果返回的方法签名不为nil,则会走到forwardInvocation
方法中,对invocation
事务进行处理,如果不处理也不会报错
好了,通过反汇编的形式又将我上面发的那一幅流程图走了一遍,这就是动态方法决议
之后进行的消息快速转发
和消息的慢速转发
。