iOS消息转发机制

时间:2022-10-30 20:53:34

iOS消息转发机制

“消息派发系统”(message-dispatch system)

若想令类能够理解某条消息,我们必须实现出对应的方法才行。但是,在编译器向类发送其无法解读的消息时并不会报错,因为在运行期可以继续向类中添加方法,所以编译器在编译时还无法确定类中到底会不会有某个方法的实现。当对象接收到无法解读的消息时,就会启动“消息转发”机制,我们可以经由此过程告诉对象应该如何处理未知消息。

消息转发分为两个阶段。第一阶段先征询接收者所属的类,看其是否能动态添加方法,已处理当前这个“未知的选择子”,这叫做“动态方法解析”。第二阶段涉及“完整的消息转发机制”。如果运行期系统已经把第一阶段执行完了,那么接收者自己就无法再以动态新增方法的手段来响应包含该选择子的消息了。此时运行期系统就会请求接收者以其他手段来处理与消息相关的方法调用。细分为两步:首先,让接收者看看有没有其他对象能处理这条消息。如果有,则运行期系统会把消息转给那个接收者,于是消息转发结束。如果没有这个“备援接收者”,则启动完整的消息转发机制,运行期系统会把与消息有关的全部细节封装到NSInvocation对象中,再给接收者最后一次机会,令其设法解决当前还未处理的这条消息。

动态方法解析:

对象在收到无法解读的消息之后,首先将调用所属类的下列类方法:

iOS消息转发机制
图1

该方法的参数就是那个未知的选择子,其返回值为布尔类型,表示这个类是否能新增一个实例方法用以处理此选择子。在继续往下执行转发机制之前,本类有一个机会处理此选择子方法。假如是一个类方法,那么将会调用另外一个方法,如图2:

iOS消息转发机制
图2

备援接收者:

当前接收者还有第二次机会处理未知的选择子,在这一步中,运行期系统会询问是否能将该消息转发给其他的接收者处理。对应的方法如图3:

iOS消息转发机制
图3

参数为未知的选择子,如当前接收者能找到备援对象,则将其返回,找不到就返回nil。通过此方案,我们可以用“组合”来模拟出“多重继承”的某些特性。在一个对象内部,可能还有一系列其他对象,该对象可以经由此方法将能够处理某选择子的相关内部对象返回,这样的话,在外界看来好像是该对象亲自处理了这些消息。

完整的消息转发:

如果转发已经到了这一步,那么唯一能做的就是启动完整的消息转发机制了。首先创建NSIvocation对象,把尚未处理的那条消息有关的细节全部封到其中。此对象包含选择子、目标、参数。在出发NSIvocation对象时,”消息派发系统“将亲自出马,把消息指派给目标对象。会调用图4的方法来转发消息:

iOS消息转发机制
图4

这个方法的实现可以写的很简单,只需要改变调用目标,使消息在新目标上得以调用即可。然而这样实现出来的方法与”备援接收者“反感所实现的方法等效,所以很少有人采用这么简单的实现方式。比较有用的实现方式为:在触发消息前,先以某种方式改变消息内容,比如追加另外一个参数,或者是改换选择子等等。实现此方法时若发现不应该由本类处理,则需要调用超类的同名方法。这样的话,集成体系中的每个类都有机会处理此调用请求,直至NSObject。如果最后调用了NSObject类的方法,那么该方法还会继而调用”doesNotRecognizeSelector:“以抛出异常,此异常表明选择子最终未能得到处理。

消息转发流程:

下面这张图描述了消息转发机制处理消息的各个步骤:根据重复理解一下过程就很明朗了(*^__^*) ……

iOS消息转发机制
图5

接收者在每一步中均有机会处理消息。步骤越往后,处理消息的代价就会越大。最好能在第一步就完成,这样的话,运行期系统就可以将此方法缓存起来。如果这个类的实例后面还收到同名的选择子,那么根本就无须启动消息转发流程。若想在第三部把消息转发给备援接收者,还不如把转发操作提前到第二部。以为第三部只是修改了调用目标,这项改动放在第二部执行的话会更加简单,不然还得创建并处理完整的NSIvocation。

以完整的例子演示动态方法解析:

为了说明消息转发机制的意义,下面示范如何以动态方法解析来实现@dynamic属性。假设要编写一个类似于”字典的对象“,它里面可以容纳其他对象,只不过开发者要直接通过属性来存取其中的数据。这个类的设计思路是:由开发者来添加数据定义,并将其声明为@dynamic,而类则会自动处理相关属性值得存放与获取操作。

本例的关在在resolveInstanceMethod:方法的实现:

iOS消息转发机制
demo地址:https://github.com/MaxIsComing/MAXAutoDictionary
参考:http://www.jianshu.com/p/60f19a844f18