向对象发送一个无法处理的消息将会导致错误,然而,在报告错误之前,运行时系统会提供给消息的接收者第二次机会去处理消息。
转发(Forwarding)
如果你向一个无法处理某个消息的对象发送了该消息,在报错之前,运行时(runtime)会向对象发送forwardInvocation:消息带有NSInvocation对象作为其唯一参数。NSInvocation对象压缩传递给它的原始信息和参数
你可以实现forwardInvocation:方法来给消息一个默认的回复,或者通过某些方式避免报错。正如其方法名称的含义,forwardInvocation:是用来将消息转发给另一个对象
想象如下场景:假如首先,你设计一个对象可以响应消息negotiate,并且你希望它的返回包含另一个对象的返回,你可以很简单的实现这个需求,通过在该对象的方法实现中传递negotiate消息给另一个对象并返回,更进一步,假设我们想让negotiate方法的返回和另一个类negotiate方法的返回完全一致。其中一种方法是通过继承,但是这不一定是最好的方法,blabla~
即使不通过继承的方法,我们也可以通过“借用”的方式实现
- (id)negotiate
{
if ( [someOtherObject respondsTo:@selector(negotiate)] )
return [someOtherObject negotiate];
return self;
}
这种做法有些笨重,尤其是如果存在很多方法想传递给其他的对象的时候,你必须实现每一个想从其他类借用的方法。更糟糕的是,这样做可能发生意料之外的事,(Moreover, it would be impossible to handle cases where you didn’t know, at the time you wrote the code, the full set of messages you might want to forward. That set might depend on events at runtime, and it might change as new methods and classes are implemented in the future.这是文档所说的意料之外的事,没看太懂)
(抛砖引玉,这上面两段是砖,这篇文档真喜欢绕圈子,绕了一个大圈子,终于绕回来了==!)
forwardInvocation:方法为这类问题提供了专门的方法(a less ad hoc solution我也搞不清楚是专门的还是临时的了)并且是动态的。大致流程如下:当一个对象因为没有方法匹配消息中的选择器而不能响应某个消息时,运行时系统(runtime system)会向对象发送forwardInvocation:消息。每一个对象(这里的对象应该特指继承NSObject的对象)都会从NSObject对象继承forwardInvocation:方法。然而NSObject类的这个方法会简单的调用doesNotRecognizeSelector:。通过重写NSObject的方法,你就可以利用forwardInvocation:将消息转发给其他对象
转发消息,forwardInvocation:需要做下面的事情:
@1 决定消息的去向
@2 将消息和原始参数传递过去
例如 消息可以被invokeWithTarget:发送
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
if ([someOtherObject respondsToSelector:
[anInvocation selector]])
[anInvocation invokeWithTarget:someOtherObject];
else
[super forwardInvocation:anInvocation];
}
转发的消息的返回值会被返回给原始的发送者(sender).所有类型的返回值都可以传递给(sender)。(including ids, structures, and double-precision floating-point numbers.)
forwardInvocation:方法可以看作是无法识别的消息(unrecognized messages)的分发中心,将它们发送给不同的接收者。或者作为中转站,将所有的消息发送给同一个目的地。它可以将一个消息转化成另一个,或者只是简单的吞噬,不返回也不报错。forwardInvocation:方法可以将多条消息同一个返回。forwardInvocation:的所为由该方法的实现过程决定。
注意:forwardInvocation:方法仅在调用了接收者不存在的方法时才会被调用。假如,你希望你的对象转发negotiate消息,那么他就不能实现negotiate方法
Forwarding and Multiple Inheritance
在Objective-C程序中,转发模仿多继承,并且可以用来达到多继承的一些效果。如图所示,对象通过转发“继承”另一个类中的方法实现
Forwarding
上图中,Warrior的一个实例转发了negotiate消息给Diplomat实例。Warrior看上去将像Diplomat一样处理negotiate消息。
转发消息的对象,就像“继承”了两个继承链。一个其本身的继承链,一个消息转发对象的继承链。在上例中,Warrior看上去既继承了自己的父类又“继承”了Diplomat
转发提供了类似多继承的很多特性。然而两者却有很大不同:多继承将不同的性能结合在单一对象中。导致庞大的,复杂的对象。相反,转发机制,将不同的职责赋予不同的对象。将复杂问题分解成小的对象,但是又通过一种方式将这些对象结合起来,并且保持对消息发送者(sender)透明。
Surrogate Objects(代理对象)
转发机制不仅仅模仿了多继承,它也使得使用轻量对象代替复杂对象成为可能。代理对象代替其他对象,并且过滤消息(The surrogate stands in for the other object and funnels messages to it)。
在The Objective-C Programming Language,“Remote Messaging” 文中提到的proxy就属于一种代理(Surrogate),proxy负责向一个遥远的接收者转发消息的管理细节(A proxy takes care of the administrative details of forwarding messages to a remote receiver)保证参数值复制,在连接中检索,等等。但它不会尝试做别的事情,它不会复制接收者的函数功能,只是简单的给接收者发送一个地址,在那里可以从另一个程序接收消息(?_?)
其他类型的代理对象也是可能的。假设,你有一个对象操作很多数据--假如它绘制了一个复杂的图形或者从硬盘上读取了一个文件的内容。创建这样一个对象需要耗费很多时间,所以你可能会懒加载-当真正需要的时候或者系统闲置的时候。这时,你就需要一个placeholder代替这个对象,以使其他对象调用正常
在这种情况下,你可以首先初始化一个不完全对象,但是是一个完全对象的轻量代理。这种对象可以自己做一些事情,像根据数据回答问题,但多数情况下,它只是用来暂时替代完全对象并转发消息给完全对象。当代理对象的forwardInvocation:方法第一次接收消息并转发时,你就要确保接收消息的对象存在,如果不存在就创建。所有的消息都通过代理对象转发给完全对象,对程序来说,代理对象和完全对象就是一个对象
Forwarding and Inheritance
尽管转发机制模拟了继承,而NSObject类不会分不清两者。respondsToSelector: 和 isKindOfClass:只会在继承链中查找,而不会在转发链中查找,打个比方,Warrior 对象如果调用下面的方法
if ( [aWarrior respondsToSelector:@selector(negotiate)] )
...
返回值一定是NO,尽管它可以接收negotiate消息并不会报错同时会返回结果。见上面多继承的图。
在多数情况下,NO都是正确的。然而。。。如果你用转发机制创建了一个代理对象(surrogate object)或者扩展了一个类的能力。转发机制在这时应该和继承一样平等对待。如果你希望你的对象的 消息转发 达到和 继承 一样的效果,你需要重写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:方法来返回准确的方法描述来回应转发消息?例如,如果一个对象可以向代理对象转发消息,你需要这样实现
- (NSMethodSignature*)methodSignatureForSelector:(SEL)selector
{
NSMethodSignature* signature = [super methodSignatureForSelector:selector];
if (!signature) {
signature = [surrogate methodSignatureForSelector:selector];
}
return signature;
}
注意:这是一种先进的技术,仅仅适合在别无他法的情况下使用,不能用来代替继承。如果你需要使用它,保证你完全明白你的类的行为和转发的类的行为