十五、浅谈消息转发

1,425 阅读4分钟

本文由快学吧个人写作,以任何形式转载请表明原文出处

一、准备资料

objc4-818.2

对应mac的版本是11.1。可根据自己的系统版本挑选可以进行调试的源码。

二、思路

  1. 无论是上一章还是本章,都是对十三章lookUpImpOrForward的一个完善。
  2. 动态方法决议之后,lookUpImpOrForward还有一个流程是 :日志和添加缓存。
  3. 那么关于方法查找的日志中都会有一些什么?

三、项目准备

  1. 为了日志中没有其他因素的干扰,所以创建一个新的mac项目。

  2. 创建一个新的mac项目,并在其中创建一个JDMan类和一个JDKid类,都继承于NSObject。

  3. 然后在JDMan.h中添加一个实例方法,并且不实现这个实例方法。然后在main.m中实例化一个JDMan的实例,并让实例调用这个实例方法。如下图 :

图片.png

图片.png

四、如何找到lookUpImpOrForward的日志

  1. 在818.2的源码中,找到lookUpImpOrForward的源码。找到done: 中的日志和缓存添加的源码。

图片.png

  1. 进入log_and_fill_cache源码,如下 :

图片.png

  1. 发现一个宏判断和一个判断条件。

(1). 宏判断的条件SUPPORT_MESSAGE_LOGGING!TARGET_OS_OSX的条件下为0,因为我创建的是mac项目,所以!TARGET_OS_OSX == 0。则SUPPORT_MESSAGE_LOGGING == 1。所以会进入if判断。

(2). objcMsgLogEnabled在源码中找到默认是false。

  1. if判断中的logMessageSend源码 :

图片.png

  1. 找到了日志存储的路径。先直接运行上面准备的项目,然后用Finder进入这个路径,查看/tmp目录下是否有msgSends前缀的日志文件。结果会发现没有。也就是说,objcMsgLogEnabled在默认为false的情况下,是不会生成日志的,那么如果让objcMsgLogEnabled为true,是否会生成日志?

  2. 如何让objcMsgLogEnabled为true。肯定不能直接在源码中修改。所以搜索objcMsgLogEnabled,发现如下函数可以为它赋值 :

图片.png

  1. 在上面创建的项目中,在main.m中,为instrumentObjcMessageSends函数做一个外部声明进行扩充,让我们可以调用它。并将main.m代码改成如下 :

图片.png

  1. 运行项目,项目会崩溃,因为未实现ceShi方法,这个无所谓。然后进入/tmp路径下,找到日志文件,打开日志 :

图片.png

图片.png

  1. 会发现在resolveInstanceMethod动态方法决议之后,方法的查找流程还会调用两个函数 :

forwardingTargetForSelectormethodSignatureForSelector。这个就是所谓的消息转发。

五、forwardingTargetForSelector:

  1. 详细信息可以找xcode提供的官方Help文档(command + shift + 0(零)),搜索forwardingTargetForSelector。这里不做详细展示。

(1). 返回值id : 想要哪个对象处理未实现的方法,就返回哪个对象。

(2). 参数aSelector : 未实现的方法的SEL。

  1. 在818.2源码中搜索forwardingTargetForSelector,发现是有两个方法的,一个类方法,一个实例方法 :

图片.png

  1. 举例 :

(1). 利用上面创建的项目。再给JDMan添加一个类方法,并且同样不在JDMan中进行实现。

图片.png

(2). 在JDMan.m中实现forwardingTargetForSelector的类方法和实例方法。

图片.png

(3). 既然转发给了JDKid,那么在JDKid.m中实现(1)图中的两个JDMan的方法。

图片.png

(4). 运行代码 :

图片.png

(5). 实例方法就用实例方法的forwardingTargetForSelector进行转发。 类方法则就用类方法的forwardingTargetForSelector进行转发。类方法不要用实例方法的,实例方法也不要用类方法的forwardingTargetForSelector

六、methodSignatureForSelector:

  1. 详细信息可以找xcode提供的官方Help文档(command + shift + 0(零)),搜索methodSignatureForSelector。这里不做详细展示。

(1). 返回值NSMethodSignature : 方法的encodingType,也就是方法的编码类型,包括方法的返回值和参数的类型信息。

(2). 参数aSelector : 未实现的方法的SEL。

  1. NSMethodSignature的初始化方法也可以在Help文档中找到 :

图片.png

  1. methodSignatureForSelector需要搭配forwardInvocation方法一起使用。

  2. 举例 :

(1). 利用上面举例中的项目,不实现forwardingTargetForSelector。JDMan.m中代码 :

图片.png

(2). 运行结果

图片.png

(3). 实例就用实例的方法,类就用类方法的。

  1. 可以只实现methodSignatureForSelector,不实现forwardInvocation中的代码,就是forwardInvocation要写出来,但是内部不写任何东西,也不会崩溃。

图片.png

图片.png

七、总结

消息转发有两种方法 :

  1. forwardingTargetForSelector,有实例方法和类方法,可以返回一个对未实现方法进行实现的对象或者类,这个对象或者类内部如果实现了这个未实现的方法,则不会报错。

  2. methodSignatureForSelector,有实例方法和类方法,会返回一个NSMethodSignature对象。相当于把未实现方法的encodingType(方法类型编码)存放到进程的调度中心里。等待着其他实现者来实现。

  3. methodSignatureForSelectorforwardInvocation搭配使用。可以不在forwardInvocation内写代码。但是必须要写出来forwardInvocation这个方法。