在编译期向类发送了其无法解读的的消息并不会报错,因为在运行期可以继续让类中添加方法,所有编译器在编译时还无法确知类中到底会不会有某个方法实现,当对象接收到无法解读的消息后,就会启动 消息转发 机制,程序员可经由此过程告诉对象应该如何处理未知消息。
在程序运行中,有时会以下异常信息
[Class function_name ] unrecognized selector sent to instanc
上段信息就是发送一个未识别的消息给实例,类定义但对象未实现function_name 方法
第一阶段先征询接受者所属的类,看其是否能动态添加方法,以处理当前“未知的选择子”,叫做“动态方法解析”。
第二阶段涉及“完整的消息转发机制”,如果运行期系统已经把第一阶段执行完了,那么接受者自己就无法再以动态新增方法的手段来响应包含该选择子的消息了。此时,运行期系统会请求接受者以其他手段来处理与消息相关的调用方法。这又细分为两小阶段。首先,请接受者看看有没有其他对象能处理这条信息。若有,则运行期系统会把消息转给那个对象,于是消息转发过程结束。若没有“备援的接受者”,则启动完成的消息转发机制,运行期系统会把与消息有关的细节全部封装到NSInvocation对象中,让接受者最后一次设法解决当前还未处理的这条消息
对象接收到无法解读的消息后,首先将调用所属类的下列类方法:
+(BOOL)resolveInstanceMethod:(SEL)selector
该方法的参数就是未知的选择子,返回类型为Boolean,表示类是否能新增一个实例方法用以处理此选择子。在继续往下执行转换机制之前,本类可用新增处理此选择子的方法,假如尚未实现的方法不是实例方法而是类方法,那么运行期系统就会调用另外一个方法,该方法与“resolveInstanceMethod:”类似,叫做”resolveClassMethod:” 。
使用这种办法的前提是:相关方法的代码实现已经写好,只等着运行的时候动态插在类里面就可以了。此方案常用来实现@dynamic 属性,比如要访问CoreData框架中NSManagedObjects对象的属性时就可用这么做,因为实现这些属性所需要的存取方法在编译器就能确定
备援接受者为处理未知的选择子提供第二次机会,嫩故能把这条消息转给其他接受者来处理。该步骤对应的处理方法如下:
-(id)forwardingTargetForSelector:(SEL)selector
方法参数代码未知的选择子,若当前接受者能找到备援对象,则将其返回,若找不到,就返回nil。通过次方案,我们可以用“组合”来模拟“多重继承”的某些特性。在一个对象内部,可能还有一系列其他对象,该对象可经由此方法将能够处理某选择子的相关内部对象返回,从外界看来,好像是该对象亲自处理这些消息。若是想在发送给备援接受者之前先修改消息内容,那就通过完整消息转发机制来做。
如果转发算法到了这一步,那么唯一能做的就是启用完整的消息转发机制,首先创建NSInvocation对象,把尚未处理的那条消息有关的全部细节都封于其中。此对象包含选择子,目标及参数。在触发NSInvocation对象时,“消息派发系统”将会把消息指派给目标对象。
此步骤会调用下列方法来转发消息:
-(void)forwardInvocation:(NSInvocation *)invocation
该方法实现简单,只需要改变调用目标,使消息在新目标上得以调用即可。然而这样实现出来的方法与“备援接受者”方案所实现的方法等效,所有很少有人采用这么简单的实现方式。比较有用的实现方式为:在触发消息前,先以某种方式改变消息内容,比如追加另外一个参数,或是改变选择子。
实现该方法时,若发现某调用操作不应由本类处理,则需要调用超类的同名方法。这样集成体系中的某个类都有机会处理此方法调用,直到NSObject,继而调用”doesNotRecognizeSelector:”以抛出异常,此异常表明选择子最终未能得到处理。
接受者在每一步中均有机会处理信息。步骤越往后,处理消息的代价就越大,最好能在第一部就能处理完,这样,运行期系统就可以将方法缓存起来,如果这个类的实例稍后收到同名选择子,就武器启动消息转发流程。若想在第三部把消息转给备援接受者,还不如提前到第二步,因为第三步只是修改了调用目标,这项改动放在第二步执行更为简单,而且不用创建并处理完整的NSInvocation
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。