本文翻译自Message Forwarding
消息转发
给一个对象发送其不能处理的消息会产生错误。可是,在发生错误之前,运行时系统给接收对象一个二次机会来处理这个消息。
转发
如果你给一个对象发送其不能处理的消息时,在产生错误之前,运行时会发送给对象一个forwardInvocation: 消息,此消息包含一个NSInvocation对象作为唯一参数----这个NSInvocation对象中包含了之前的原始消息和参数。
你可以实现forwardInvocation: 方法来提供一个默认消息响应,或者用某种其他方式来避免错误。就像名称所说那样,forwardInvocation: 一般用于将消息转发给其他对象。
要了解转发的适用范围和目的,设想下面的场景:首先,假设你正在设计一个对象,它可以响应一个叫negotiate的消息,并且你想要它的响应中包含其他种类对象的响应。你可以很容易地完成它:只需要在你的negotiate方法体中,给其他对象再发送一个negotiate消息。
再想深一点,设想你想要你的对象能够响应negotiate消息,但是其实现却其他类中。完成它的一种方法就是让你的类继承于其他类。可是,这种情况有可能做不到。也许有很多原因导致你的类和实现了negotiate方法的类在不同的继承体系中。
即使你的类不能继承到negotiate方法,你还可以通过”借“的方式来实现一个特别的版本,就是将这个消息直接传递给一个其他类的实例:
- (id)negotiate
{
if ( [someOtherObject respondsTo:@selector(negotiate)] )
return [someOtherObject negotiate];
return self;
}
这种方式可能有一点笨,特别是当你想把许多消息都赚给其他对象时。你必须把从其他类借到的方法依次实现。此外,有时候你不太可能处理到那些你不知道的情况,因为你只是写了那些你当时准备转发的消息代码。这一些消息可能取决于运行时,并且很有可能在未来变成一些新的方法和类。
通过forwardInvocation: 消息提供的第二次机会是一个不太特别的解决方案,而且是个动态方案。其工作过程如下:当一个对象不能响应一个消息时(因为它没有一个可以匹配消息中选择器的方法),运行时系统会给对象发送一个forwardInvocation: 消息。每一个对象都从NSObject类中继承了forwardInvocation: 方法。可是,NSObject的实现版本仅仅调用了doesNotRecognizeSelector: 方法。通过覆盖NSObject的版本并进行你自身的实现,你可以利用这次机会通过forwardInvocation: 将消息转发给其他对象。
要转发一个消息,所有的forwardInvocation: 方法需要按下面的方式去做:
- 决定这个消息要去哪,并且
- 带着原始参数给它发送过去
这个消息可以通过invokeWithTarget: 方法进行发送:
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
if ([someOtherObject respondsToSelector:
[anInvocation selector]])
[anInvocation invokeWithTarget:someOtherObject];
else
[super forwardInvocation:anInvocation];
}
转发出的消息的返回值会被返回给原始的接收者。所有类型的返回值都可以被传递给接收者,包括id、结构体和双精度浮点数等。
forwardInvocation: 方法对于不能识别的消息扮演着一个分发中心的角色,给这些消息打包发送给不同的接收者。或者它也可以当做一个中转站,发送所有消息到同一个目的地。它可以将一个消息转换为另一个,或者直接”吞掉“某些消息使其不产生响应或错误。一个forwardInvocation: 方法还可以将多个消息合并到一个响应中。forwardInvocation: 方法所做的事情都由实现者决定。可是,它提供的一种将对象链接到到转发链中的机会为程序设计提供了新的可能性。
注意:
只有当正常的接收者不能调用一个存在的方法时,forwardInvocation: 方法才会收到待处理消息。例如,如果你想要你的对象转发negotiate消息给另一个对象,那么它自己就不能包含negotiate方法。如果存在此方法,消息就绝不会到达forwardInvocation: 中。
查看更多关于转发和调用的信息,请在Foundation框架参考中查看NSInvocation类的说明。
转发和多继承
转发模拟了继承,并且可以被用于在Objective-C程序中实现出一些多继承的效果。如图表5-1所示,通过转发来响应消息的对象就好像是从另一个类中”继承“了方法实现一样。
图表5-1在这个插图中,一个Warrior类的实例转发negotiate消息给了一个Diplomat类的实例。Warrior看起来就好像是一个Diplomat一样。就好像它响应了negotiate消息,并且从实际上看,它确实响应了(虽然是一个Diplomat完成的工作)。
转发消息的对象从而通过两个继承体系的分支中“继承”了方法----它自己的分支和响应消息的那个对象的分支。在上面的例子中,和自己的父类一样,看起来就好像Warrior类也继承于Diplomat类。
转发提供了大多数的多继承特性。可是,在二者之间还是存在一点重要的不同:多继承将不同的能力合并在了一个对象中。而转发,却将不同的功能分配给了不同的对象。它将问题分解到小的对象中,却用一种对消息发送者透明的方式将这些对象联系起来。
替代者对象
转发不仅模拟了多继承,它还使开发一个可以代表或者“隐藏”大量对象的轻量级对象成为可能。替代者替代了其他对象并且将消息过滤给它们。
The Objective-C Programming Language中的“Remote Messaging”中讨论的代理就是这样一个替代者。代理负责了将消息转发给远程接收者的所有管理细节,确保参数值通在连接中被拷贝和取回,等等。但是它并没有做更多工作;代理并没有复制远程对象的功能,只是简单地给远程对象一个本地地址,即一个可以从其他应用程序中接收消息的位置。
其他种类的替代者对象也可以做到这些。假设,你有一个可以操作许多数据的对象----也许它创建了一个复杂的图片或者从磁盘上的文件中读取了内容。创建这个对象也许会非常耗时,所以你更想进行懒加载----当真的需要时或者是系统资源空闲时。同时,你至少需要一个占位对象来保证应用程序中的其他对象可以正常工作。
在这种情况下,你并不需要创建一个完整的对象,而是一个轻量级的替代者。此对象可以做一些自己的事情,比如回答关于数据的问题,但多数情况下,它只是为那个大对象占用一个位置,当真的需要时,就转发消息给它。当替代者的forwardInvocation: 收到消息并指向其他对象时,它可以确保改对象存在或者当不存在时可以创建出来。大对象的所有消息都会通过替代者传送过来,因此,无论程序的其他部分如何关心,替代者和大对象都是相同的。
转发和继承
虽然转发模拟了继承,但NSObject类从不会将二者弄混。像respondsToSelector: 和 isKindOfClass: 的这种方法只会在继承体系中查找,而绝不会在转发链中查找。例如,如果一个Warrior对象被询问是否可以响应negotiate消息时,
if ( [aWarrior respondsToSelector:@selector(negotiate)] )
...
答案是NO,尽管它可以接收negotiate消息,不发生错误并能够进行响应,在某种意义上,通过转发消息给Diplomat对象(见图表5-1)。
在许多情况下,NO就是正确答案。但也可能不是。如果你通过转发来创建一个替代者对象或者扩展类的能力,转发机制应该和继承一样透明。如果你想让你的对象看起来就像真的继承于转发消息的对象,你就需要重新实现respondsToSelector: 和isKindOfClass: 方法,以便包含你的转发规则。
- (BOOL)respondsToSelector:(SEL)aSelector
{
if ( [super respondsToSelector:aSelector] )
return YES;
else {
/* Here, test whether the aSelector message can *
* be forwarded to another object and whether that *
* object can respond to it. Return YES if it can. */
}
return NO;
}
除了respondsToSelector: 和isKindOfClass: 方法之外,instancesRespondToSelector: 方法也应该模拟转发规则。如果协议被使用了,conformsToProtocol: 方法也应该加到修改列表中。相似地,如果一个对象转发了任何它收到的远程消息,它应该包含一个methodSignatureForSelector: 方法的版本,它可以返回最终要响应的转发消息的准确描述。例如,如果一个对象正要转发消息给它的替代者,你可以向下面这样实现methodSignatureForSelector: :
- (NSMethodSignature*)methodSignatureForSelector:(SEL)selector
{
NSMethodSignature* signature = [super methodSignatureForSelector:selector];
if (!signature) {
signature = [surrogate methodSignatureForSelector:selector];
}
return signature;
}
你还可以考虑将转发规则放到一些私有代码中,并且让所有的待转发方法都调用它(包括forwardInvocation:)。
注意:
这是一项高级技术,只适合那些没有其他解决方案时的场景。不能用它来代替继承。如果你必须使用此项技术,确保你完整地理解了使用转发的类和转发到的类的所有行为。
在本节中提到的方法都在Foundation框架指南的NSObject类的说明中都有描述。要查看invokeWithTarget: 的更多信息,请查看Foundation框架指南的NSInvocation类的说明。
网友评论