转发流程定位
接上一篇文章的最后,我们找了方法未实现时的默认函数,下面我们调一个未实现的方法,看看具体的调用栈.
|
|
bt下看看堆栈:
|
|
在main后和doesNotRecognizeSelector方法前分别调用了 __forwarding_prep_0__ 和 __forwarding__ 这两个函数,我们猜测是OC的消息转发逻辑,那么如何去验证呢,老办法,我们先去源码中看看。
一无所获。
那我们再想想其他办法,我们分别点开这两个函数的堆栈,在头部发现了一些信息
|
|
看来这两个函数的定义可能在CoreFoundation库中,那么我们自然就去开源库里面找找看,CoreFoundation的源码我们是找到了,打开发现还是没有上面的函数,看来并未全部开源,那么还是其他办法去探索么?
答案是有的,还可以通过反汇编工具来查看CF的动态库文件,这里推荐Hopper Disassembler.
在Hopper中,我们找到了如下伪代码:
|
|
通过上面伪代码,我们提炼出了几个比较重要的函数,即我们常说的消息转发的流程(除了_forwardStackInvocation这个函数,这个函数我们下面在说)原来出处在CoreFoundation中,知道了出处,我们再来验证下他们的逻辑。
转发流程验证
resolveInstanceMethod && resolveClassMethod 动态方法决议
该部分的源码就在objc中,上一篇解释消息慢查询的时候只是一笔带过了,这里我们再来分析下:
|
|
看了源码,我们知道了只要重写一下上面两个方法,系统就会调一次这两方法,相当于一个钩子,我们可以在钩子里面动态的给该类加上缺失的方法,正常逻辑ok,那如果我们只是重写但是不动态添加方法实现会怎么样呢?我们来验证下:
|
|
奇怪的事情发生了,resolveInstanceMethod进来了两次,第一次进来比较好理解,但是第二次是从哪里进来的呢?是不是因为递归查找的原因?我们打个断点看看两个打印时的bt:
第一次
|
|
第二次
|
|
可以看到,第一次堆栈就是正常的lookUpImpOrForward查找逻辑触发的,但是第二次的触发确是在消息转发中获取到函数签名后,那如何验证呢?很简单,我们去实现下methodSignatureForSelector这个方法看看:
|
|
果然,第二次打印的函数变成了这个_forwardStackInvocation,这是哪来的呢?请看上面的CoreFoundation伪代码。大胆猜测就是如果没有实现自己的函数签名的话,系统还是用之前的函数签名(fun0)去调用一次lookUpImpOrForward,实现后换成CoreFoundation内部的(_forwardStackInvocation)函数签名去调用一次lookUpImpOrForward.
forwardingTargetForSelector 快速转发
快速转发需要做的操作比较简单,返回一个实现了该方法的类。
methodSignatureForSelector && forwardInvocation 慢速转发
慢速转发有下面两个有意思的点:
趣点一:方法签名并未指定格式,你可以返回任意正确的格式,比如我返回下面这种格式也ok:
|
|
项目中应该没有那个函数带了这么多参数吧。
但是下面这个格式就不行:
|
|
因为缺少最基本的sel参数。
趣点二: 只需要重写forwardInvocation函数,即使是一个空函数,系统即认为当前被转发的方法已经被人处理,不会抛出异常。这里我们就可以做一些其他的事情了。
小结
通过以上探索,我们不仅掌握了OC消息转发的实现方式,还了解了转发逻辑内部的实现原理,也为我们在生产环境的灵活使用打下基础,比如我们可以通过消息转发机制实现方法调用的防护工作等比较重要的内容,更多的功能需要我们进一步的探索和实践。