消息转发机制的出现
回想一下,在我们日常的程序开发中,是不是经常会遇到下面这种情况:
自定义的Atom类 示例我们自定义了一个类,然后再程序中调用某个方法,但是这个方法在该类中找不到相应实现,系统提示我们unrecognized selector sent to instance,表明该对象无法解读此消息,但作为一门高级的编程语言,难道Objective-C不做任何事就会让程序崩溃掉吗,答案当然是...不可能!作为一门动态语言,Objective-C充分的利用了它的语言特性,在程序运行期间通过一些方法来给开发者机会阻断程序这样的崩溃,给对象和消息更多的机会来完成成功的调用,而这其中所用用到的技术就是消息转发机制。
什么是消息转发机制
消息转发机制,听起来好像很厉害的一个概念,那么消息转发机制到底是什么意思呢,我的理解是消息转发机制就是系统为了更好的解决对象收到无法解读的消息这件事,然后自己创建出的一个模式,包含了一系列方法,就像程序崩溃前的防守阵营一样,用来阻止程序直接走向崩溃。下面用一张图来形象的展示一下消息转发机制的构成:
消息转发机制由图中可以直观的看到,消息转发机制主要包含三个步骤:
1:动态方法解析阶段
2:备用接收者阶段
3:完整消息转发阶段
如果程序经过这三个阶段之后都没能解读所调用的消息,那么程序会调走向最后的灰色提示,doesNotRecognizeSelector方法,抛出异常,需要注意一点的是,上面三个步骤,越往后,处理消息的代价越大。好啦,下面我们就来具体介绍一下这三个步骤吧!
动态方法解析阶段
这是消息转发的第一阶段,处在这个阶段的时候,主要有两个方法来帮你解决问题:
1:+(BOOL)resolveClassMethod:(SEL)sel
2:+(BOOL)resolveInstanceMethod:(SEL)sel
顾名思义,当方法是类方法时调用1,当方法为实例方法时,调用2。这个方法设计的目的是为了给类利用 class_addMethod 添加方法的机会。下面来看一下具体实现的例子:
示例由代码可知,在Atom类中我并未定义一个名为instanceTest的实例方法,所以编译器报了黄色的警告,但我在Atom的实现文件中通过+(BOOL)resolveInstanceMethod:(SEL)sel这个方法在运行时动态的添加了方法的实现,最终程序正常运行。
关于class_addMethod(__unsafe_unretained Class cls, SEL name, IMP imp, const char *types)方法:
这里对于这个方法的参数做一点补充:
cls:被添加方法的目标类
name:新方法的选择器
imp:实现这个方法的函数的地址
types:描述方法参数的数据类型的字符串,例如这个例子中我写的id functionForInstance(id self,SEL _cmd),就对应“@@:”,这种表达方式叫Type Encodings,是官方定义好的,更全的展示我在这贴两张官方的图:
type encodings method encodings备用接收者阶段
这是消息转发的第二阶段,如果第一阶段没有成功,就会来到这个阶段,在这个阶段主要利用的方法是- (id)forwardingTargetForSelector:(SEL)aSelector,此时,运行时询问能否把消息转给其他接收者处理,也就是此时系统给了个将这个 SEL 转给其他对象的机会,具体示例如图所示:
示例我们新定义了一个类,名为Helper,在该类中我们实现了instanceTest实例方法,当系统运行时在Atom类中找不到方法的相应实现,于是来到- (id)forwardingTargetForSelector:(SEL)aSelector这个方法,请求备用者响应,如果备用者能响应则将此消息转给新对象执行。
完整消息转发阶段
如果前两个阶段都不能解决问题,系统就会来到最后这个第三阶段,完整消息转发阶段,这是消息转发流程的最后一个环节,与第二阶段不同,此阶段可以将消息转发给多个对象,这个阶段主要利用的方法是- (void)forwardInvocation:(NSInvocation *)anInvocation,但是注意一点,使用这个方法必须同时重写- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector这个方法,消息转发机制使用从这个方法中获取的信息来创建NSInvocation对象,具体示例如图:
Helper实现文件 Helper2实现文件 Atom实现文件 示例在这个例子中,我们新建了一个Helper2类,同样实现了instanceTest实例方法,可以看到,在这个例子中最后程序顺利执行了,同时Helper和Helper2都响应了这条消息,同时也能发现,最后的返回值是最后一个处理消息转发对象的对应方法的返回值。
补充一点,关于methodSignatureForSelector,methodSignatureForSelector用于描述被转发的消息,系统会调用methodSignatureForSelector:方法,尝试获得一个方法签名。如果获取不到,则直接调用doesNotRecognizeSelector抛出异常。如果能获取,则返回非nil:创建一个 NSlnvocation 并传给forwardInvocation:。
网友评论