在Runtime知识体系中讲过拦截调用,当发送未知消息时,如果不经过方法拦截,程序就会崩溃。
unrecognized selector sent to instance 0x7faa2a132c0
调试过程中如果看到输出这句话,我们马上就能知道某个对象并没有实现向他发送的消息。
我们知道消息发送的机制,通过superclass指针逐级向上查找该消息所对应的方法实现。如果直到根类都没有找到这个方法的实现,运行时会通过补救机制,继续尝试查找方法的实现。
下面来了解一下补救机制:
直到最后一步消息无法处理后,我们的App就崩溃了,随后我们就看到了熟悉的unrecognized selector...
resolveInstanceMethod:
resolveInstanceMethod: 和 resolveClassMethod: 方法允许你为一个给定的 selector 动态的提供方法的实现。
OC 方法在底层的C函数的实现中需要至少两个参数:self 和 _cmd。使用 class_addMethod 函数,你能够添加一个函数到一个类来作为方法使用。
forwardingTargetForSelector:
如果一个对象实现了这个方法,并且返回了一个非空(以及非 self)的结果,返回的对象会用来作为一个新的接收对象,随后消息会被重新派发给这个新对象。(很明显,如果你在这个方法中返回了self,那这段代码将会坠入无限循环。)
如果你这段方法在一个非 root 的类中实现,并且如果这个类根据给定的selector什么都不作返回,那么你应该返回一个 执行父类的实现后返回的结果。
这个方法为对象在开销大的多的 forwardInvocation: 方法接管之前提供了一次转发未知消息的机会。这对你只是想简单的重新定位消息到另一个对象是非常有用的,并且相对普通转发更快一个数量级。如果转发的目的是捕捉到NSInvocation,或者操作参数,亦或者是在转发过程中返回一个值,那这个方法就没有用了。
forwardInvocation:
当对象接受到一条自己不能响应的消息时,运行时会给接收者一次机会来把消息委托给另一个接收者。他委托的消息是通过NSInvocation对象来表示的,然后将这个对象作为 forwardInvocation: 的参数。接收者收到 forwardInvocation: 这条消息后可以选择转发这个NSInvacation对象给其他接收对象。(如果这个接收对象也不能响应这条消息,他也会给一次转发这条消息的机会。)
因此 forwardInvocation: 允许在两个对象之间通过某个消息来建立关系。转发给其他对象的这种行为,从某种意义上来说,他“继承”了他所转发给的对象的一些特征。
注意
为了响应这个你无法识别的方法,你除了 forwardInvocation: 方法外,还必须重写 methodSignatureForSelector: 方法。在转发消息的机制中会从 methodSignatureForSelector: 方法来创建NSInvocation对象。所以你必须为给定的 selector 提供一个合适的 method signature ,可以通过预先设置一个或者向另一个对象请求一个。
以上,是苹果官方文档对这三个关键方法的解释。
总结
resolveInstanceMethod: 会为对象或类新增一个方法。如果此时这个类是个系统原生的类,比如 NSArray ,你向他发送了一条 setValue: forKey: 的方法,这本身就是一次错发。此时如果你为他添加这个方法,这个方法一般来说就是冗余的。
forwardInvocation: 必须要经过 methodSignatureForSelector: 方法来获得一个NSInvocation,开销比较大。苹果在 forwardingTargetForSelector 的discussion中也说这个方法是一个相对开销多的多的方法。
forwardingTargetForSelector: 这个方法目的单纯,就是转发给另一个对象,别的他什么都不干,相对以上两个方法,更适合重写。
既然 forwardingTargetForSelector: 方法能够转发给别其他对象,那我们可以创建一个类,所有的没查找到的方法全部转发给这个类,由他来动态的实现。而这个类中应该有一个安全的实现方法来动态的代替原方法的实现。
整理
1.创建一个接收未知消息的类,暂且称之为 Protector
2.创建一个 NSObject 的分类
3.在分类中重写 forwardingTargetForSelector: ,在这个方 法中截获未实现的方法,转发给 Protector。并为 Protector 动态的添加未实现的方法,最后返回 Protector 的实例对象。
4.在分类中新增一个安全的方法实现,来作为 Protector 接收到的未知消息的实现
具体Demo见:Demo地址