本文由快学吧个人写作,以任何形式转载请表明原文出处
一、准备资料
对应mac的版本是11.1。可根据自己的系统版本挑选可以进行调试的源码。
二、思路
- 无论是上一章还是本章,都是对十三章
lookUpImpOrForward的一个完善。 - 动态方法决议之后,
lookUpImpOrForward还有一个流程是 :日志和添加缓存。 - 那么关于方法查找的日志中都会有一些什么?
三、项目准备
-
为了日志中没有其他因素的干扰,所以创建一个新的mac项目。
-
创建一个新的mac项目,并在其中创建一个JDMan类和一个JDKid类,都继承于NSObject。
-
然后在JDMan.h中添加一个实例方法,并且不实现这个实例方法。然后在main.m中实例化一个JDMan的实例,并让实例调用这个实例方法。如下图 :
四、如何找到lookUpImpOrForward的日志
- 在818.2的源码中,找到
lookUpImpOrForward的源码。找到done:中的日志和缓存添加的源码。
- 进入
log_and_fill_cache源码,如下 :
- 发现一个宏判断和一个判断条件。
(1). 宏判断的条件SUPPORT_MESSAGE_LOGGING在!TARGET_OS_OSX的条件下为0,因为我创建的是mac项目,所以!TARGET_OS_OSX == 0。则SUPPORT_MESSAGE_LOGGING == 1。所以会进入if判断。
(2). objcMsgLogEnabled在源码中找到默认是false。
if判断中的logMessageSend源码 :
-
找到了日志存储的路径。先直接运行上面准备的项目,然后用Finder进入这个路径,查看/tmp目录下是否有
msgSends前缀的日志文件。结果会发现没有。也就是说,objcMsgLogEnabled在默认为false的情况下,是不会生成日志的,那么如果让objcMsgLogEnabled为true,是否会生成日志? -
如何让
objcMsgLogEnabled为true。肯定不能直接在源码中修改。所以搜索objcMsgLogEnabled,发现如下函数可以为它赋值 :
- 在上面创建的项目中,在main.m中,为
instrumentObjcMessageSends函数做一个外部声明进行扩充,让我们可以调用它。并将main.m代码改成如下 :
- 运行项目,项目会崩溃,因为未实现
ceShi方法,这个无所谓。然后进入/tmp路径下,找到日志文件,打开日志 :
- 会发现在
resolveInstanceMethod动态方法决议之后,方法的查找流程还会调用两个函数 :
forwardingTargetForSelector和methodSignatureForSelector。这个就是所谓的消息转发。
五、forwardingTargetForSelector:
- 详细信息可以找xcode提供的官方
Help文档(command + shift + 0(零)),搜索forwardingTargetForSelector。这里不做详细展示。
(1). 返回值id : 想要哪个对象处理未实现的方法,就返回哪个对象。
(2). 参数aSelector : 未实现的方法的SEL。
- 在818.2源码中搜索
forwardingTargetForSelector,发现是有两个方法的,一个类方法,一个实例方法 :
- 举例 :
(1). 利用上面创建的项目。再给JDMan添加一个类方法,并且同样不在JDMan中进行实现。
(2). 在JDMan.m中实现forwardingTargetForSelector的类方法和实例方法。
(3). 既然转发给了JDKid,那么在JDKid.m中实现(1)图中的两个JDMan的方法。
(4). 运行代码 :
(5). 实例方法就用实例方法的forwardingTargetForSelector进行转发。
类方法则就用类方法的forwardingTargetForSelector进行转发。类方法不要用实例方法的,实例方法也不要用类方法的forwardingTargetForSelector。
六、methodSignatureForSelector:
- 详细信息可以找xcode提供的官方
Help文档(command + shift + 0(零)),搜索methodSignatureForSelector。这里不做详细展示。
(1). 返回值NSMethodSignature : 方法的encodingType,也就是方法的编码类型,包括方法的返回值和参数的类型信息。
(2). 参数aSelector : 未实现的方法的SEL。
NSMethodSignature的初始化方法也可以在Help文档中找到 :
-
methodSignatureForSelector需要搭配forwardInvocation方法一起使用。 -
举例 :
(1). 利用上面举例中的项目,不实现forwardingTargetForSelector。JDMan.m中代码 :
(2). 运行结果
(3). 实例就用实例的方法,类就用类方法的。
- 可以只实现
methodSignatureForSelector,不实现forwardInvocation中的代码,就是forwardInvocation要写出来,但是内部不写任何东西,也不会崩溃。
七、总结
消息转发有两种方法 :
-
forwardingTargetForSelector,有实例方法和类方法,可以返回一个对未实现方法进行实现的对象或者类,这个对象或者类内部如果实现了这个未实现的方法,则不会报错。 -
methodSignatureForSelector,有实例方法和类方法,会返回一个NSMethodSignature对象。相当于把未实现方法的encodingType(方法类型编码)存放到进程的调度中心里。等待着其他实现者来实现。 -
methodSignatureForSelector和forwardInvocation搭配使用。可以不在forwardInvocation内写代码。但是必须要写出来forwardInvocation这个方法。