还原OC对象的方法调用轨迹(下)

引言

我们了解类的本质构成及“类继承体系”, superclass 指针确立了继承关系,而 isa 指针描述了当前实例对象所属的类,并却能够看出对象位于“类继承体系”的哪一部分。如果某对象传递消息:
1
id returnValue = [object fun: parameter];
编译器会把这条消息转换为如下函数:
1
id returnValue = objc_msgSend(object, @selector(fun:), parameter);
objc_msgSend 函数会依据接受者与方法的类型来调用适当的方法。为了完成此操作,该函数需要在接受者所属的类中搜寻其“方法列表”(methods)(该函数会将匹配结果换存在“缓存表”(cache)里面,每个类都有这样一块缓存,便于下次还向该类发送此方法),如果能找到与方法名称相符的方法,就跳转至相对应的实现代码。若是找不到就沿着“类继承体系”的“方法调用轨迹”继续向上查找,找到了合适的方法之后再跳转至实现代码,在此之前都称为消息传递阶段。如果最终还是没找到相符的方法,那就执行“消息转发”(message forwarding)操作。

消息转发(message forwarding)

在编译期间向某类发送了其无法解读的消息并不会报错,因为在运行期可以继续向类中添加方法,所以编译器在编译时还无法确认类中会不会有该方法的实现。当最终都没能解读此消息后,就会启动 消息转发(message forwarding)机制。如果经过消息转发阶段仍未找到对象执行解读此消息,结果则会调用“doesNotRecognizeSelector”方法抛出异常以程序崩溃而告终。不过开发者在编写自己的类时,可在消息转发阶段设置“挂钩”,用以执行预定的逻辑,而不至于应用程序崩溃。 消息转发阶段分为两大阶段:第一阶段会先征询接受者所属的类,看其是否为该方法动态添加了方法的实现部分,称为“动态方法解析”(dynamic method resolution);如果仍没解决问题会来到第二阶段,如果来到了该阶段,代表着接受者自己就无法再以动态新增方法的手段来响应该消息了。此时,运行期系统会请求接受者以其他手段来处理与消息相关的方法调用,又细分为两步:首先请接受者看有没有其他对象“备援的接受者”(replacement receiver)来处理这条消息,若存在,则把消息转发给那个对象,消息转发结束。若没有,则启动“完整的消息转发机制”(full forwarding mechanism),运行期系统会把与该消息有关所有的细节封装到 NSInvocation 对象中,再给接受者最后一次机会,令其设法解决当前还未处理的消息。

1、动态方法解析(dynamic method resolution)


对象在收到无法解读的消息后,首先会调用其所属类的下列方法:
1
+(BOOL)resolveInstanceMethod: (SEL)selector //方法类型为对象方法
或者
1
+(BOOL)resolveClassMethod: (SEL)selector //方法类型为类方法
表示这个类是否能够新增一个实例方法来处理这个消息。使用这个方法的前提是,该类已经把相关方法的实现代码准备好了,就等着运行的时候动态插在类里面就可以了。关于这的方案的应用
Effective Objective-C 2.0Matt Galloway(著) 爱飞翔(译)
这本书举了“@dynamic”属性的实现为例子,实际开发中也可能会被用来“注入”调试信息,或者收集用户信息等。

2、备援接受者(replacement receiver)


该阶段当前接受者还有第二次机会处理这个未知的消息,会调用
1
+(id)forwardingTargetForSelector: (SEL)selector 
来获取备援接受者对象。一个对象的内部,可能还有一系列其他对象,该对象可经由此方法将能够处理该消息的相关内部对象当作结果返回。

3、完整的消息转发机制(full forwarding mechanism)

首先创建 NSInvocation 对象,把与尚未处理的那条消息有关的全部细节都封装在里面,这个对象包含方法、目标接受者及参数。 在触发 NSInvocation 对象时,“消息派发系统”(message-dispatch system)会调用
1
-(void)forwardingInvocation: (NSInvocation *)invocation 
把消息指派给目标对象。该方法的实现内容大致分两种:第一种,简单的修改目标,使该消息在新目标上得以调用即可。由于此方法功能跟“备援接受者”阶段的效果一样,所以很少有人采用;第二种就是在消息被触发前,以某种方式改变消息本体的参数或更换消息等。这个阶段也可以调用父类的同名上述方法,这样继承体系中的每个类都有机会处理此调用请求,直至 NSObject 基类,继而还是会调用“doesNotRecognizeSelector”方法抛出异常,俗称程序崩溃。

总结

消息传递阶段:作为面向对象高级编程语言几乎都有与之相对应的函数调用轨迹。 消息转发阶段:接受者在每一步都有机会处理这个消息,只不过越往后,处理消息的代价就越大。“动态绑定”的机制为Object-C语言赋予了动态语言所拥有的特性,也因为此灵活性在代码执行速度上就不及“静态绑定”的函数调用操作那样迅速了,即使在类的内部缓存着曾经调用过的方法。消息转发全流程如下图:

参考文献:

第二章:对象、消息、运行期

Effective Objective-C 2.0Matt Galloway(著) 爱飞翔(译)