前言
上一篇我们打印了 unrecognized经典的崩溃信息,通过全局搜索doesNotRecognizeSelector或者unrecognized selector sent to instance,在源码中搜索方式查找到底层源码的实现,在方法查找流程中如果最后imp还是没有查找到,会调用forward_imp
forward_imp = _objc_msgForward_impcache,源码查看下 _objc_msgForward_impcache的底层实现,全局搜索_objc_msgForward_impcache
- __objc_msgForward_impcache底层是汇编实现,主要代码 b __objc_msgForward
- __objc_msgForward中TailCallFunctionPointer是个宏,前面探究过就是跳转imp。x17寄存器存放的是imp,从汇编中可以看出跟x17有关系的就是__objc_forward_handler
- 全局搜索__objc_forward_handler 汇编中没有具体的实现,那就不在汇编中,可能在C/C++源码中,全局搜索objc_forward_handler,源码如下
_objc_fatal中报错熟悉不,经典的崩溃信息报错方法没有实现
在快速和慢速查找流程过程中没有找到imp,难道就直接崩溃,不给一次机会的嘛。不行必须给次机会,不然我不服气,系统还是干不过我哈,给了次机会就是动态方法决议
动态方法决议
在探究慢速查找流程lookUpImpOrForward中,如果没有查找到imp就会走动态方法决议流程resolveMethod_locked
下面看看resolveMethod_locked动态方法决议到底干了什么,源码如下
- 首先判断cls是否是元类
- 如果不是元类只是普通类,那么说明调用的实例方法跳转resolveInstanceMethod流程
- 如果是元类,那么说明调用的是类方法跳转resolveClassMethod流程
- lookUpImpOrForwardTryCache快速查找和慢速查找sel对应的imp 然后返回imp
resolveInstanceMethod方法
- 首先创建resolveInstanceMethod 的SEL resolve_sel
- 根据lookUpImpOrNilTryCache (cls, resolve_sel, cls->ISA(true))知道resolveInstanceMethod是类方法,通过快速和慢速查找流程查找resolve_sel对应的imp,缓存resolveInstanceMethod方法
- 直接通过msg(cls, resolve_sel, sel)给类发送消息,从这里也能看到resolveInstanceMethod是类方法
lookUpImpOrNilTryCache(inst, sel, cls)快速和慢速查找流程
- 通过lookUpImpOrNilTryCache来确定resolveInstanceMethod方法中有没有实现sel对应的imp
- 如果实现了,缓存中没有,进入lookUpImpOrForward查找到sel对应imp插入缓存,调用imp查找流程结束
- 如果没有实现,缓存中没有,进入lookUpImpOrForward查找,sel没有查找到对应的imp,此时imp = forward_imp,动态方法决议只调用一次,此时会走done_unlock和done流程,既sel和forward_imp插入缓存,进行消息转发
##resolveClassMethod方法
- resolveClassMethod在NSobject中已经实现,只要元类初始化就可以了,目的是缓存在元类中
- 调用resolveClassMethod类方法,目的是实现可能resolveClassMethod“方法中动态实现sel对应的imp
- imp = lookUpImpOrNilTryCache(inst, sel, cls) 缓存sel对应的imp,不管imp有没有动态添加,如果没有缓存的就是forward_imp
lookUpImpOrNilTryCache方法
lookUpImpOrNilTryCache方法名字,可以理解就是查找imp或者nil尽可能的通过查询cache的方式,在resolveInstanceMethod方法和resolveClassMethod方法都调用lookUpImpOrNilTryCache
首先最后一个参数默认是behavior = 0,LOOKUP_NIL = 4, behavior|LOOKUP_NIL 大于等于LOOKUP_NIL
判断cls是否初始化一般都会初始化的
缓存中查找
- 在缓存中查找sel对应的imp
- 如果imp存在跳转done流程
- 判断是否有共享缓存给系统底层库用的
- 如果缓存中没有查询到imp,进入慢速查找流程
慢速查找流程
- 慢速查找流程中,behavior= 4 ,4 & 2 = 0进入动态方法决议,所以不会一直循环
- 最重要的如果没有查询到此时imp= forward_imp,跳转lookUpImpOrForward中的done_unlock和done流程,插入缓存,返回forward_imp
done流程
- done流程: (behavior & LOOKUP_NIL) 且 imp = _objc_msgForward_impcache,如果缓存中的是forward_imp,就直接返回nil,否者返回的imp,LOOKUP_NIL条件就是来查找是否动态添加了imp还有就是将imp插入缓存
lookUpImpOrNilTryCache的主要作用通过LOOKUP_NIL来控制插入缓存,不管sel对应的imp有没有实现,还有就是如果imp返回了有值那么一定是在动态方法决议中动态实现了imp
resolveInstanceMethod实例探究
在崩溃之前确实调用了resolveInstanceMethod方法
疑问:为什么会调用两次resolveInstanceMethod方法呢 第一次是走动态方法决议系统自动向resolveInstanceMethod发送消息,那么第二次是怎么调用的呢?
标记
动态添加sayTest2方法
在main函数里面 还是调用sayTest方法 通过动态添加的sayTest2 结果打印sayTest2
- resolveInstanceMethod只调用一次,因为动态添加了sayTest2方法lookUpImpOrForwardTryCache直接获取imp,直接调用imp,查找流程结束
- 崩溃也解决了动态方法决议系统给了一次机会
- 具体流程:resolveMethod_locked–> resolveInstanceMethod –> 调用resolveInstanceMethod –> lookUpImpOrNilTryCache(inst, sel, cls) –> lookUpImpOrForwardTryCache–> 调用imp
resolveClassMethod实例探究
实现resolveClassMethod方法
在main函数里面调用sayHello 通过动态添加的sayKC 结果打印sayKC
- 在崩溃之前确实调用了resolveClassMethod方法,而且调用了两次,调用两次的逻辑和resolveInstanceMethod方法调用两次是一样的
- 调用resolveClassMethod以后,会去查找lookUpImpOrNilTryCache有没有具体动态实现sel对应的imp,元类的缓存中此时有sel对应的imp,这个imp是forward_imp。lookUpImpOrNilTryCache里面有判断直接返回nil,此时直接到resolveInstanceMethod查找,因为类方法实际上就是元类中的实例方法
- 如果最后还是没有实现lookUpImpOrForwardTryCache获取到forward_imp进入消息转发流程
resolveClassMethod特殊之处
标记
整合动态方法决议
resolveClassMethod方法中如果没有动态添加类方法,会调用元类中的resolveInstanceMethod。那么能不能把resolveInstanceMethod写到一个公用类中,使类方法和实例方法都能调用
- 实例方法查找流程:对象 –> 类 –>直到根类(NSObject) –> nil
- 类方法查找流程:类 –> 元类 –>直到根类(NSObject) –> nil
到最后都找到NSObject类中,所以这个公用类就是NSObject分类
实例方法是类方法调用,系统都自动调用了resolveInstanceMethod方法,和上面探究的吻合。
动态方法决议优点
- 可以统一处理方法崩溃的问题,出现方法崩溃可以上报服务器,或者跳转到首页
- 如果项目中是不同的模块你可以根据命名不同,进行业务的区别
- 这种方式叫切面编程熟成AOP
AOP和OOP的区别
- OOP:实际上是对对象的属性和行为的封装,功能相同的抽取出来单独封装,强依赖性,高耦合
- AOP:是处理某个步骤和阶段的,从中进行切面的提取,有重复的操作行为,AOP就可以提取出来,运用动态代理,实现程序功能的统一维护,依赖性小,耦合度小,单独把AOP提取出来的功能移除也不会对主代码造成影响。AOP更像一个三维的纵轴,平面内的各个类有共同逻辑的通过AOP串联起来,本身平面内的各个类没有任何的关联
消息转发
快速和慢速查找流程没有查询到,动态决议方法也没有查找到,下面就会进入消息转发流程,但是在objc4-818.2源码中没有发现相关的源码,CoreFunction提供的源码也不详细查询不到。苹果还是提供了日志辅助功能
日志辅助
通过lookUpImpOrForward –> log_and_fill_cache –> logMessageSend,进入logMessageSend 看到源码的实现