一.探索前需知
1.1 上篇讨论了objc_msgSend消息慢速查找的流程,对象方法的调用会从类的方法列表methodlist里去寻找,当找不到时会去当前类的父类直至NSObject,如果一直没有找到程序就会抛出经典的错误异常:unrecognized selector sent to class 0x100002290
1.2 类方法也是一样,只不过类方法会在元类里去寻找,然后到元类的父类最后也会来到NSObject. 不过在这里肯定有一个疑问的点,OC是一门动态语言并且也是一门相对安全的语言(如果不安全,苹果公司也不会用OC来进行开发),难道方法在慢速流程找不到时就不做任何处理,直接让其崩溃的吗?那么就来到本章的探索之旅.
(附:上篇博客地址 juejin.cn/post/684490…)
二.消息转发之初探
2.1 当发送个没有实现的消息时,系统底层到底做了哪些处理.如下图:
其中saySomething在 LGStudent中没有实现,在父类以及NSObject中同样没有对应的实现只是在LGStudent.h 里有这个方法的声明. 关于这种底层方法的调用我们只能查看汇编 .我们先在代码里打个断点、打开工程里的Debug -> Debug Workflow - > Always Show Disassembly就是打开汇编调试、我们在方法上打个断点:
运行代码发现代码会进入:
其实这里就是正常的消息查找流程首先是快速查找(在cache里进行查找)、然后是慢速查找(methodTable里.
这块流程就稍微走一下呀,毕竟之前的文章都已经分析过了,这方法肯定在快速查找和慢速查找都找不到,那么它接下来走到哪里呢?
发现汇编会走到_class_resolveMethod 里:
然后走到_class_resolveInstanceMethod 方法里面去:
然后走到lookUpImpOrNil 方法里面去:
这里又走了个lookUpImpOrForward 是在寻找一次(这里开个上帝视角,查询有没有在resolveInstanceMethod方法里动态添加处理SEL的方法)
我们接着到 _objc_msgForward_impcache里面去
进入 ___forwarding___ 发现 51行有个 forwardingTargetForSelector
发现 93行有个 methodSignatureForSelector
发现 183行有个 forwardInvocation
原来如此 我们能够得知系统在做消息转发的时候可能、可能、可能(重要的事说三遍)
做了下面的流程: _class_resolveInstanceMethod -- > forwardingTargetForSelector --> methodSignatureForSelector --> forwardInvocation
2.2 有的盆友可能对汇编不太了解也不太会调试,那怎么才能看到消息转发阶段系统的调用呢?
在这里可以用到苹果的私有API.
我们可以通过extern void instrumentObjcMessageSends(BOOL)
API打印来查看
结果会输出到/private/tmp/msgSend-XXX
文件路径下:
打开这个msgSends-15803:
原来的原来方法在调用过程中,在报 doesNotRecognizeSelector 前 经过了 resolveInstanceMethod -- > forwardingTargetForSelector --> methodSignatureForSelector --> forwardInvocation这些过程.那么我们再深入的去了解下吧.
三.消息转发之进阶
3.1 resolveInstanceMethod
当方法进行转发时第一步会进入这里,我们看下 _class_resolveMethod的实现(在这里会有个亮点也可以说是个雷点等会再说):
_class_resolveInstanceMethod进去:
在这里我们来逐步分析下:
if (! lookUpImpOrNil(cls->ISA(), SEL_resolveInstanceMethod, cls, NO/*initialize*/, YES/*cache*/, NO/*resolver*/))
{
// Resolver not implemented.
return;
}
首先这个方法lookUpImpOrNil查找在类里有没有 resolveInstanceMethod 方法的实现,有的盆友会说我的类里没有实现这个方法、说明lookUpImpOrNil 为false,前面加个! 为True就会今日 if判断里.直接return掉,其实忘了一点本类没实现,可能父类或根类实现了啊,看下源码:
是不是实现了,所以只要继承于NSObject,这个判断里永远不会走.
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
bool resolved = msg(cls, SEL_resolveInstanceMethod, sel);
这两句代码就是判断 在resolveInstanceMethod 里是否可以能处理走消息转发的SEL,但是刚刚大家也看到了 NSObject 里返回NO,代表不处理 ,怎么才能返回YES 处理这个消息转发的SEL 呢?
只能在本类里重写这个方法了.并且_class_resolveInstanceMethod这个方法的注释也解释了这一点:
/***********************************************************************
* _class_resolveInstanceMethod
* Call +resolveInstanceMethod, looking for a method to be added to class cls.
* cls may be a metaclass or a non-meta class.
* Does not check if the method already exists.
**********************************************************************/
响应resolveInstanceMethod ,寻找方法并且添加.
在我们的类里是不是重新写了这个方法 ,并且 class_addMethod 动态来添加方法,将sayHello的IMP 给了 saySothing.相当于调用saySothing就调用了sayHello.
但有的朋友就是没有实现resolveInstanceMethod这个方法咋办呢?
3.2 forwardingTargetForSelector
在这里我们全局 control + shift + o 来搜索 forwardingTargetForSelector
发现底层实现就这两行代码,不要紧我们来看下苹果官网是咋说的:
文档说明了该方法的前世今生 , 总结如下 :
- 1️⃣ : 该方法的目的 , 说白了就是你搞不定的方法 , 就交给别人处理 . 但是不能返回
self
,否则会一直找不到 , 陷入死循环 .- 2️⃣ : 如果不实现或者返回
nil
,会走到forwardInvocation
: 方法进行处理 .- 3️⃣ : 被转发消息的接受者参数和返回值等需要和原方法相同 .
也就是说在这个方法里你可以使用备援接受者,你自己本类不能处理 交给这个备援接受者去实现了这个方法 , 就交由其去处理.
你LGStudent 对象不能说话,当我强行调用的话 我就交给我的老师LGTeacher,我的老师有这个技能包可以说话.
还有的朋友这个方法也不想去实现,自己的活就自己做,干嘛麻烦老师.
3.3 methodSignatureForSelector
同样command + shift + o 去搜索 methodSignatureForSelector:
同样去官方文档去查看:
discussion:该方法用于协议的实现。此方法还用于必须创建NSInvocation对象的情况,例如在消息转发期间.如果对象维护委托或能够处理其未直接实现的消息,则应重写此方法以返回适当的方法签名.
原来这个方法是为NSInvocation对象 来服务的,返回这个创建对象的相应的方法签名
四. 思维发散
上面分析的一切一切都是实例方法在消息转发机制的流程,其实类方法本质和对象方法差不多,只是调用的方法会不同,_class_resolveInstanceMethod 变成了 _class_resolveClassMethod,有兴趣的伙伴也可以自己试下,在类方法的消息转发流程有点不一样,还记得刚刚提到的雷点吗?再把刚刚那个图一下:
类方法肯定会走 _class_resolveClassMethod(cls, sel, inst); 为什么下面
if (!lookUpImpOrNil(cls, sel, inst,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/))
{
// 对象方法 决议
_class_resolveInstanceMethod(cls, sel, inst)
}
会调用resolveInstanceMethod?
就是当自己的类 resolveClassMethod没实现时,会调用resolveInstanceMethod.
其实看过之前文章的伙伴应该能知道 类方法存在元类里,随着ISA的走位图会一直往上找找到元类的父类--> 根元类 --> NSObject , 所以最后会找到NSObject 的实例方法 所以会调用 _class_resolveInstanceMethod.
最后给大家消息转发流程的流程图:
五.总结
- 经过
objc_msgSend
方法快速查找和慢速查找不到结果之后就进入消息动态解析. - 动态解析过程
_class_resolveMethod()
,有处理就按照处理的来,没有处理,就进入消息快速转发阶段. - 消息快速转发阶段
forwardingTargedForSelector()
,有处理,就按照交给处理的对象来实现,有没有交给其他对象处理,进入慢速转发阶段. - 消息慢速转发阶段
methodSignatureForSelector()
,进行方法签名,把方法丢出去,forwardInvocation()
来对消息处理. - 若以上中间步骤没有进行,则就进入到了
doesNotRecognizeSelector
报错.