向不处理该消息的对象发送消息是错误的。但是,在宣布错误之前,运行时系统会给接收对象第二次处理消息的机会
。
转发
如果将消息发送到不处理该消息的对象,则在宣布错误之前,运行时会向该对象发送一个forwardInvocation:message
,其中NSInvocation
对象作为其唯一参数,NSInvocation
对象将封装原始消息及其传递的参数。
您可以实现fforwardInvocation:
方法来给消息提供默认响应
,或者以其他方式避免错误。顾名思义,forwardInvocation:通常用于将消息转发到另一个对象
。
要了解转发的范围和意图,请设想以下场景:首先,假设您正在设计一个可以响应名为negotiate的消息的对象,并且希望其响应包含另一种对象的响应。通过将协商消息传递给所实现的协商方法主体中的其他对象,可以很容易地完成此操作。
更进一步,假设您希望对象对negotiate消息的响应与在另一个类中实现的响应完全相同。实现这一点的一种方法是让您的类从另一个类继承该方法。然而,这样安排事情可能是不可能的。您的类和实现negotiate的类位于继承层次结构的不同分支中可能有很好的原因。
即使您的类不能继承negotiate
方法,您仍然可以通过实现该方法的一个版本来“借用”该方法
,该方法只需将消息传递给另一个类的实例:
- (id)negotiate { if ( [someOtherObject respondsTo:@selector(negotiate)] ) return [someOtherObject negotiate]; return self; }
这种方式可能会有点麻烦,特别是如果有很多消息需要对象传递给另一个对象。你必须实现一个方法来覆盖你想从另一个类借用的每个方法。而且,你可能不想知道你在哪里写了完整的代码。该集合可能依赖于运行时的事件,并且在将来实现新方法和类时可能会发生变化。
forwardInvocation
提供的第二个机会是:message为这个问题提供了一个不那么特别的解决方案,而且是动态的
,而不是静态的。它的工作原理
是这样的:当一个对象因为没有与消息中的选择器匹配的方法而无法响应消息时,运行时系统通过发送 forwardInvocation:message 通知对象。
每个对象都从 NSObject
类继承一个 forwardInvocation:
方法。但是,NSObject的方法版本只是调用doesNotRecognizeSelector:
。通过重写NSObject的版本并实现自己的版本,您可以利用forwardInvocation:message
提供的机会将消息转发到其他对象
。
若要转发消息,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:.。
有关转发和调用的更多信息,请参阅《基础框架参考》中的NSInvocation类规范。
转发和多重继承
转发模拟继承,可以用于将多重继承的一些效果借给Objective-C程序。如图5-1所示,通过转发消息来响应消息的对象似乎借用或“继承”在另一个类中定义的方法实现。
Figure 5-1 Forwarding
在本例中,Warrior(战士)类的一个实例将negotiate消息转发给Diplomat(外交官)类的实例。这位战士看起来像个外交官一样谈判。它似乎对negotiate信息做出了回应,而且出于所有实际目的,它确实做出了回应(尽管这项工作实际上是一名外交官在做)。
因此,转发消息的对象从继承层次结构的两个分支“继承”方法它自己的分支
和响应消息的对象的分支
。在上面的例子中,Warrior类似乎继承了depolator以及它自己的超类。
转发提供了您通常希望从多重继承中获得的大多数功能。然而,两者之间有一个重要的区别
:多重继承在一个对象中结合了不同的功能。它倾向于大的,多方面的对象
。另一方面,转发将单独的责任分配给不同的对象。它将问题分解为更小的对象,但以对消息发送者透明的方式关联这些对象
。
代理对象
转发不仅模拟多重继承,还可以开发表示或“覆盖”更重要对象的轻量级对象。代理代表另一个对象
并将消息传递给它
。
在 _Objective-C _编程语言的“远程消息传递”中讨论的代理就是这样一个代理。代理负责将消息转发到远程接收方的管理细节,确保参数值在连接中被复制和检索,等等。但它不尝试做其他事情;它不复制远程对象的功能,而只是给远程对象一个本地地址,一个它可以在另一个应用程序中接收消息的地方。
其他类型的代理对象也是可能的。例如,假设您有一个对象可以处理大量数据—可能它会创建一个复杂的图像或读取磁盘上文件的内容。设置此对象可能很耗时,因此您更喜欢在真正需要它或系统资源暂时空闲时懒洋洋地进行设置。同时,为了使应用程序中的其他对象正常工作,您至少需要此对象的占位符。
在这种情况下,您可以首先创建
,而不是完整的对象,而是它的轻量级代理
。这个对象可以自己做一些事情,比如回答有关数据的问题,但大多数情况下,它只会为较大的对象保留一个位置,当时间到了,它会将消息转发给它。当代理的forwardInvocation:
方法第一次接收到发送给另一个对象的消息时,它将确保该对象存在,如果不存在
,则会创建
该对象。较大对象的所有消息都经过代理项,因此,就程序的其余部分而言,代理项和较大对象将是相同的。
转发和继承
虽然转发模仿继承,但NSObject
类从不混淆两者。像respondsToSelector:
和isKindOfClass:
这样的方法只查看继承层次结构,而不查看转发链
。例如,如果一个Warrior对象被询问是否响应协商消息,
if ( [aWarrior respondsToSelector:@selector(negotiate)] ) ...
答案是否定的,尽管它可以毫无差错地接收到谈判信息,并在某种意义上通过将其转发给外交官来作出回应。(见图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:,instanceRespondToSelector:方法还应镜像转发算法。如果使用协议
,conformsToProtocol:
方法也应该添加到列表
中。
类似地,如果一个对象转发它接收到的任何远程消息,它应该有一个methodSignatureForSelector:
的版本,该版本可以返回对转发消息做出最终响应的方法的准确描述;例如,如果一个对象能够将消息转发到其代理项,则可以实现methodSignatureForSelector:如下所示:
- (NSMethodSignature*)methodSignatureForSelector:(SEL)selector { NSMethodSignature* signature = [super methodSignatureForSelector:selector]; if (!signature) { signature = [surrogate methodSignatureForSelector:selector]; } return signature; }
您可以考虑将转发算法放在私有代码中的某个地方,并将所有这些方法forwardInvocation:included,调用它。
注意:这是一种先进的技术,只适用于没有其他解决方案的情况。它不是用来替代继承的。如果必须使用此技术,请确保完全了解执行转发的类和要转发到的类的行为。