论objc_msgSend消息机制之消息转发

327 阅读7分钟

一.探索前需知

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就是打开汇编调试、我们在方法上打个断点:

运行代码发现代码会进入:

saySomthing : objc_msgSend. 我们在这行上打上断点并且按住control+xcode中step into进去发现指令会进入系统的libobjc.A.dylib的库里.

其实这里就是正常的消息查找流程首先是快速查找(在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.

最后给大家消息转发流程的流程图:

五.总结

  1. 经过objc_msgSend方法快速查找和慢速查找不到结果之后就进入消息动态解析.
  2. 动态解析过程_class_resolveMethod(),有处理就按照处理的来,没有处理,就进入消息快速转发阶段.
  3. 消息快速转发阶段forwardingTargedForSelector(),有处理,就按照交给处理的对象来实现,有没有交给其他对象处理,进入慢速转发阶段.
  4. 消息慢速转发阶段methodSignatureForSelector(),进行方法签名,把方法丢出去,forwardInvocation()来对消息处理.
  5. 若以上中间步骤没有进行,则就进入到了doesNotRecognizeSelector报错.