什么是消息转发机制?
由于OC是一门动态语言,可以在运行期继续向类中添加方法,所以在编译器向类发送未实现的方法并不会报错,当对象接受到无法解读的消息后,会启动“消息转发”机制,程序员可经过此过程告诉对象应该如何处理未知消息。
实际场景:
在我们开发中可能会经常遇到以下报错:
这段信息是由NSObject的“doesNotRecognizeSelector:”方法抛出的,这个异常说明我们在崩溃前做的所有处理都未能阻止程序崩溃。
消息转发的两大阶段:
动态方法解析:
1>第一阶段
+ (BOOL)resolveInstanceMethod:(SEL)sel
首先询问接受者所属的类是否能够动态添加方法,以处理当前未能正确解读的未知方法。
返回值:
YES:当接受者类中找到其它可执行的方法时,消息转发结束,一切正常。
NO:无法找到可执行方法,接下来会执行- (id)forwardingTargetForSelector:(SEL)aSelector 方法。
2>第二阶段:
如果第一阶段执行完毕后,扔无法正确解读调用的方法,那么此后将不在允许以动态新增的办法来响应被调用的方法。此后,运行期间系统选择执行以下两个步骤:
步骤一:(备援接受者)
- (id)forwardingTargetForSelector:(SEL)aSelector
当前接受者第二次有机会处理未解读的方法,运行期系统会询问是否可以转交其它接受者(eg:B)处理,如果B类中有的话交由B类对象处理这个未知方法,消息转发结束,一切正常,程序也不会崩溃。我们称B类为备援接受者(这里可以模拟实现多继承的特性)。
返回值:如果找到备援接受者,则返回备援接受者对象,反之返回nil。
步骤二:(完整的消息转发)
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
- (void)forwardInvocation:(NSInvocation *)anInvocation
如果没有备援接受者,则会启动完整的消息转发机制。运行期系统会把于未知方法的所有信息封装到NSInvocation对象中,进行最后一波处理挽救,如果还是未能解决,就会调用上述所说的“doesNotRecognizeSelector”方法,出现以上类似的崩溃信息。
步骤二中的两个方法一定要同时出现,配合使用才会起作用。
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector,这个函数让重载方有机会抛出一个函数的签名,再由后面的forwardInvocation:去执行。这个方法中需要注意的是,在我们创建签名signature时,会执行
signature = [NSMethodSignature signatureWithObjCTypes:"@@:@"];
这样一句代码,其中@@:@表示的是一个函数类型,有返回值,有参数,并且返回值和参数都是NSObject类型的。具体规则可参照文档:developer.apple.com/library/content/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html#//apple_ref/doc/uid/TP40008048-CH100-SW1
当然如果你不关心规则的话,同样可以事先写好一个类型一样的方法,然后使用method_getTypeEncoding方法获取types(不过需要去除字符串中的数字,eg:"v24@0:8@16",去除数字则为"v@:@",表示带有一个NSObject类型参数并且无返回值的方法)。
在方法- (void)forwardInvocation:(NSInvocation *)anInvocation中,参数anInvocation包含未解读方法的所有信息,包括方法SEl,所有参数,目标Target。在这个方法中我们依然可以实现类似于备援选择者一样的方法,调用方法[anInvocation invokeWithTarget:[SpareClass new]];则会在SpareClass中寻找未解读方法,并尝试解析。这个方法很简单,所以不是经常用(如果用这个还不如直接在步骤一的备援选择者中实现,何必绕一大圈)。经常使用的场景是修改消息的内容,比如追加一个参数,更改方法,等等。
以上全是理论(感觉还没描述清楚,( >﹏<。)~呜呜呜……),最后上Demo,Demo中的注释我感觉还蛮清楚的,根据自己的需要,解除相应注释,跑一遍吧。
Demo地址: https://github.com/LYFsy/Message_forwarding