前言
有以下的简单代码,声明了一个类 Doge,里面有一个实例方法run,但是并不实现run方法
在
main函数里,创建一个Doge实例dog并调用run
很明显,运行后是会崩溃的。
这也是一个日常开发中很常见的崩溃,清理代码的时候可能一不小心就误删了某个测试方法实现。
在声明还保留的情况下没运行到该方法是不会发生崩溃的,这时候就相当尴尬。
那么如何避免这种问题呢,首先想到的是创建一个测试类扩展,并在里面声明并实现测试方法
但是这样操作每次都要创建很多类扩展文件,清理起来也相当麻烦,那么是否有办法能一劳永逸呢?
动态方法解析 resolveInstanceMethod
熟悉方法调用的同学知道其实质是发送消息,也就耳熟能详的objc_msgSend;
不熟悉的同学请看方法调用/消息发送详解;
而SEL和IMP又是什么关系呢,用书来比喻的话SEL相当于目录,页码相当于IMP(指向函数具体实现的指针),书页的具体内容就相当于函数实现。
为什么提到这个呢,因为我们是方法调用导致的崩溃,所以从方法调用流程入手找解决方案是最好的。
在慢速查找方法实现(也就是lookUpImpOrForward的过程)中,找不到方法(找不到IMP)时就会调用resolveMethod_locked方法。
进入resolveMethod_locked,在这里会判断传入的cls参数是否为元类
- 如果是,则说明原本执行的方法为类方法
- 如果否,则说明原本执行的方法为实例方法
两者的虽然分为了两个分支,但处理大同小异,进入resolveInstanceMethod,可以看到该方法会通过lookUpImpOrNilTryCache获取重新获取IMP。
resolveInstanceMethod流程图如下
所以我们需要做的就是在出问题的类(或者其父类,甚至是NSObject)里面重写resolveInstanceMethod方法并并为缺失实现的方法添加IMP
此时,在resolveInstanceMethod断点,可以看到能获取到新的IMP
放开断点,成功解决崩溃
快速消息转发 forwardingTargetForSelector
当resolveInstanceMethod没有处理成功的时候,对象就会调用forwardingTargetForSelector,通过返回别的对象处理该消息。
简单来说,就是我的Doge虽然有run这个功能,但是他年龄没到没法实现。我知道Cat也能run我就让Cat对象去run
可以看到,也是成功处理了崩溃
标准消息转发 methodSignatureForSelector & forwardInvocation
如果消息无人认领,也就是forwardingTargetForSelector无法处理的时候,可以通过标准消息转发流程处理。
首先通过class_getInstanceMethod和method_getTypeEncoding获取另一个已经实现的方法类型并用其创建一个NSMethodSignature。
然后重写forwardInvocation并把上述获取签名的方法赋值到anInvocation.selector即可
总结
至此,消息转发流程的全流程已经结束。
上述的解决方案都在发生问题的类(Doge)中编写代码,如果想统一处理的话只需要创建NSObject的类扩展并在里面编写即可。
最后,借用一张来源网络的流程图作为收尾