发送消息到不处理该消息的对象会发生错误。然而,在声明错误之前,运行时系统给接收对象第二次机会处理该消息。
转发
如果发送消息到不处理该消息的对象,在声明错误之前,运行时给该对象发送forwardInvocation:
消息,NSInvocation
对象作为唯一参数。NSInvocation
对象封装原始消息和需要传递的参数。
可以实现 forwardInvocation:
方法,提供一个默认消息响应,或者以其他方式避免错误。顾名思义, forwardInvocation:
通常用来将消息转发给另一个对象
为了看到转发的范围和目的,想象以下场景:首先假设,你正在设计一个叫做negotiate
的对象可以响应消息,你希望它能响应另一个对象的响应。你可以通过传递negotiate
消息到你实现的negotiate
方法中的另一个对象。
更近一步,假设希望对象精确的响应negotiate
消息,则需要在另一个类中实现。实现这个目标的一个方法是让类继承其他类的方法。然而,它不可能以这种方式安排事情。也许存在充分的理由为什么你的类和实现negotiate
的类在不同分支的继承层次结构中。
即使类不能继承negotiate
方法,仍然可以通过实现一个版本的方法来“借用”它,该方法只是简单的将信息传递给另一个类的实例:
-(id)negotiate
{
if ( [someOtherObject respondsTo:@selector(negotiate)] )
return [someOtherObject negotiate];
return self;
}
这种方式有点麻烦,特别是对象要传递大量消息到另一个对象。你必须实现一个方法来覆盖每个从其他类借来的方法。此外,这种方法不能处理你不知道的情况。例如,在写代码的时候,你想转发所有的消息。这取决于运行时的事件,有可能在将来作为新方法和类实现。
消息提供第二次机会,提供了一个相对不那么特殊的解决方案,该方案是动态的而非静态的。它的工作原理是:当一个对象因为没有一个方法匹配消息中的选择器而无法响应消息时,运行时系统通过发送一个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:
做什么是由系统决定的。然而,它提供一个机会,使得转发链接中的链接对象为程序设计提供可能。
注意:方法只有在他们不调用名义上接收者的现有方法的情况下才处理消息。例如,如果你希望你的对象转发
negotiate
消息到另一个对象,则对象不能有自己的negotiate
方法。如果是这样,消息永远不会到forwardInvocation:
。
关于转发和调用的更多信息,参见基础框架引用中NSInvocation
类规范。
转发和多重继承
转发类似继承,可以用来支持Objective-C 编程的多重继承的某些效果。如图5-1所示,一个对象通过转发消息来响应消息,该对象似乎是借或者“继承”另一个类中实现的方法。
在这幅图中,Warrior 类的一个实例转发negotiate
消息给Diplomat 类的实例。战士好像作为一个外交官来谈判。好像响应了negotiate
消息,事实上,它确实响应了(尽管实际上是外交官来做这些事)。
转发消息的对象“继承”方法,该方法来自继承层次结构的两个分支,该对象自己的分支来响应消息。在上面的例子中,看起来好像Warrior 类继承Diplomat 。
转发提供了大部分多重继承的功能。然而,两者之间有个重要的区别:多重继承在单一对象上结合了不同的功能。它更加强大,多层面对象。另一方面,转发分配单独的职责给不同的对象。它将问题分解为更小的对象,但是以一种方式将对象联合,该方式对消息发送者是透明的。
代理对象
转发不仅模仿多重继承,还可以开发轻量级对象代表或“覆盖”更实质的对象。代理代替其他对象并传送消息给它。
Objective-C 编程语言中“远程消息传递”中描述了该代理。代理负责管理消息转发到远程接受者,确保复制参数值和恢复链接,等等。但不尝试做其他。它不与远程对象的功能重复,只是简单的给远程对象一个本地地址,该地址可以在另一个应用程序中接收消息。
其他类型的代理对象也是可以的。假设,如果有个对象操作大量数据,它也许会创建一个复杂的图片或者读取磁盘上的文件内容。设置该对象可能非常耗时,所以为了简单,只有真正需要的时候或者系统资源暂时闲置时使用。同时,为了保证其他对象在应用程序正常运行,该对象至少需要一个占位符。
在这样的情况下,你可以首先创建,不是成熟的对象,但是时一个轻量级的代理。这个对象可以做些自己的事情,比如回答数据问题,但更多的是为大对象占个位置,当时间到了转发消息给大对象。当代理forwardInvocation:
方法首先接收一条消息传递给另一个对象,它将确保对象存在,如果不存在则创建。所有大对象的消息都是通过代理,因此,对于其余程序而言,代理和大对象是一样的。
转发和继承
尽管转发模仿继承,NSObject类不会混淆两者。像respondsToSelector:
方法和isKindOfClass:
方法只查看继承层次结构,不在转发链上。例如,访问一个战士对象确认是否响应谈判消息。
if ( [aWarrior respondsToSelector:@selector(negotiate)] )
...
答案是NO,尽管它可以接收谈判消息,并且没有错误以及通过转发消息给外交官来响应消息(如图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;
}
可以考虑将转发算法放在私有代码处。
注意:这是一种先进技术,只有在没有其他解决方案可行的情况下才适用。它并不打算取代继承。如果你必须使用这种技术,确保你完全理解转发类和你要转发的类的行为。
在本章中提到的方法在基础框架引用中NSObject类规范中有说明。关于 invokeWithTarget:
更多信息,参阅基础框架引用中的NSInvocation 类规范。
官方原文地址:
https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtForwarding.html#//apple_ref/doc/uid/TP40008048-CH105-SW1