前言
好好学习,不急不躁。每天进步一点点
上篇文章讲解消息动态决议,消息动态决议在函数方法未实现时,提供的补救措施。在动态决议之后,系统分别执行了forwardingTargetForSelector
,methodSignatureForSelector
这两个函数流程,这就是消息转发流程,接下来我们展开讲解,什么是消息转发流程。
消息转发
快速转发
查找imp流程进入forwardingTargetForSelector
内部,但由于该类没有实现aSelector,所以程序依然会报错:
在上一篇文章中讲述了公共类的一点思路,那么我们在forwardingTargetForSelector
函数中,进行SEL
的重定向试试,执行其他类的SEL
;
QLYTeacher
类实现了sayHi
函数,将QLYPerson
的sayHi
函数,重定向至QLYTeacher
类,程序运行完全没有问题,并且代码清晰,不臃肿,很好的实现了公共函数抽取封装的思路:如果其他类未实现某方法,可以在公用类中实现,或直接在公用类中动态添加aSelector;
但如果在QLYTeacher
中,也没有实现sayHi
函数,程序继续报错,消息将执行到methodSignatureForSelector
函数—-慢速转发流程中;
慢速转发
在QLYPerson
类实现慢速查找,系统还是崩溃,并切没有执行QLYPerson
类的慢速查找流程;
原因:
在QLYPerson
类实现快速查找,通过重定向至QLYTeacher
,所以会执行QLYTeacher
类的查找SEL的imp
流程,然后执行QLYTeacher
的动态决议,QLYTeacher
类的快速查找流程,但由于QLYTeacher
没有实现快速查找流程,所以直接崩溃在QLYTeacher
内部;
所以,为了查看慢速流程,暂时先把QLYPerson
类的快速查找流程屏蔽了;
执行了慢速查找流程,但依然崩溃,这是因为,慢速查找流程,需要搭配另一个函数forwardInvocation
一块执行,并且需要返回NSMethodSignature
参数;
v@:
: 默认的参数编码;type不能传空值,必须传入一个可识别的签名;
至此,慢速查找流程结束;但也没有任何的业务处理;同时,forwardInvocation
仅是声明,它有什么用处呢?
forwardInvocation
的作用:存储慢速查找流程事务,等待下一次调用;
接下来查探一下,anInvocation是否存储当前流程;
慢速查找流程,就是为了兼容程序中的错误而存在的,如果调用了某个未实现的方法,但又不需要任何的业务处理,慢速查找流程,就会为了完善程序,从而帮我们规避这种错误而产生的系统崩溃,让我们的系统更加健全,完美;
所以,如果我们在NSObject基类中,实现了如此的慢速查找,那么就能规避全系统的崩溃;但是使用慢速查找流程,也表示执行了一套imp的查找流程,性能
上有不少的消耗
;
转发流程
通过上面的内容,对消息转发的快速查找流程与慢速查找流程有了一定的了解,但是在底层,它的消息转发流程是怎样的呢?这需要我们继续探究;
通过崩溃信息,我们了解到崩溃是由于方法未找到而产生崩溃,使用bt
指令,打印出整个堆栈的信息,可以看到崩溃信息是来自CoreFoundation
框架的doesNotRecognizeSelector
函数,并且在main
函数之前执行了___forwarding___
,_CF_forwarding_prep_0
两个函数,所以我们着重来看下这几个函数的业务逻辑;
由于CoreFoundation
框架未开源,想要了解消息转发原理,得用新工具:
- 1、先下载Hopper Disassembler工具;
- 2、获取
CoreFoundation
框架的可执行文件; - 3、使用Hopper Disassembler打开
CoreFoundation
框架的可执行文件;
接下来开启反汇编操作;
查询forwarding
函数源码,顺着源码查看,底层思路先判断当前class是否存在,如果class不存在,则执行_objc_msgSend
函数获取class,如果存在则执行_objc_msgSend_stret
获取class,接着判断是否为taggedpointer小对象类型,然后进行一系列的位移操作,之后我们就能看到消息快速转发的逻辑forwardingTargetForSelector:
如果class无法响应快速转发流程,则执行慢速转发:
在底层执行的慢速转发函数,与之前设想的不一样,底层执行的是forwardStackInvocation:
函数,而这个函数并没有体现在OC层面,而是底层才有的设计,所以这个函数注定无法响应,按照底层跳转逻辑,接下来将执行loc_64c19
代码块逻辑: