Message Forwarding
给一个对象发消息,如果这个对象不处理的话,那么将会产生一个错误。但是在抛出错误之前,runtime
系统给你提供了让你处理消息的机会。
转发
给一个对象发消息,如果这个对象不处理的话,那么将会产生一个错误。但是在抛出错误之前,runtime
系统会给这个对象发送 forwardInvocation:
消息,并带了一个 NSInvocation
对象作为唯一参数。这个对象包含了原始的消息和参数。
你可以实现 forwardInvocation:
来给这个消息提供一个默认的响应,或者忽略这个错误消息。正如这个方法名字一样,forwardInvocation:
经常用来转发消息到其他对象。
为了看到转发的意图的范围,设想一下下边的场景:首先,设想一下,你设计了一个类可以响应 negotiate
消息。你想让他响应这个消息的同时也包含其他类型对象的响应。你可以很容易的在 negotiate
的实现中把这个消息传递给其他对象。
在想一下,如果你想让你的对象响应这个消息结果正好是在其他类中实现的结果,有一个方法就是让你的类继承于哪个类,然而,这样处理并不是很好。你的类,和实现了 negotiate
的类是继承链中不同的分支。
即使你的类不能继承 negotiate
方法,但你可以通过把这个消息传递给另一个类的实例对象来借用这个方法。
- (id)negotiate
{
if ( [someOtherObject respondsTo:@selector(negotiate)] )
return [someOtherObject negotiate];
return self;
}
这样做的话,不够灵活,尤其是你这个对象有大量的消息想转发给另外一个类的对象的时候。你必须实现一个方法来覆盖你想要从其他类借用过来的方法。此外,在你写代码的时候,很可能不知道所有要转发的消息。这些类和方法可能在运行时被修改,应该依赖于运行时事件。
forwardInvocation:
提供了第二个机会来让你提供一个不太临时的动态而不是静态的解决方案。就像这样一样:当一个对象由于无法找到消息中选择器所对应的方法而无法响应这个消息的时候,runtime
系统将会给这个对象发送 forwardInvocation:
消息。 每一个继承于 NSObject
的类都会有这个方法。但是在 NSObject
中只是简单地调用了 doesNotRecognizeSelector:
,通过重写这个方法来利用 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
转发到另个一对象,如果你实现了这个方法,那forwardInvocation:
永远不会被调用。
更多关于转发和调用的信息,请看 NSInvocation
。
继承和转发
继承转发,在 OC
中的多重继承是很有用的,在下图中,一个对象响应一个信息就像借用或者继承在其他类中定义的方法实现一样。
在这张图中,Warrior
类的实例对象转发消息 negotiate
到 Diplomat
类的实例对象,战士会像外交官一样谈判,似乎他在响应 negotiate
消息(其实是 Diplomat
在做所有的工作)。
这个对象转发了一个消息,因此从两个层级关系分别 [继承] 了方法 --- 他自己的分支,和这个对象应答消息的分支,上边的例子就像 Warrior
类继承了他的父类,又继承了 Diplomat
类一样。
转发提供了你想要从多重继承得到的大多数特征。然而,两个有很重要的不同:多重继承把很多功能集成到了一个对象上,使其变成一个大而全的对象。转发则是把不同的任务分配个不同的对象,它把问题分解到小的对象上,在某种程度上,就像联合所有的对象公开的消息发送者。
代理对象
转发不仅模仿了对象继承,也使得开发一些轻量级的对象来代替那些繁重的对象成为可能。
在 Objective-C
语言中的远程传递消息中描述了这个代理。代理负责消息转发到远端的接受者,同时确保参数被正确的复制和纠正消息链,等等。但他并不试图做其他的东西,它不会去复制远程对象的功能,而是为远端对象提供一个可以在其他应用接受消息的本地地址。
其他类型的代理对象也是可以的。例如,你有一个对象来处理的大量的数据,可能他创建了一个复杂的图像,或者是从磁盘读取文件。这些对象的操作是很耗时的,你最好当真正需要或者系统空闲的时候,在去处理它。同时你也要有一个默认值,以便在这个应用的其他对象可以正常调用它。
在这种情况下,你可以初始化但并不使它具有全部功能,它自己可以干一些事,比如响应一些数据请求,但是大部分的情况是,在合适的时间把消息转发到它持有的大的对象上边,当代理对象的 forwardInvocation:
方法第一次收到转向另一个对象的消息时,它将确保这个对象是存在的,如果不存在,则创建它。所有消息都会通过这个代理对象,对于程序的其他而言,代理对象和大对象是一样的。
转发和继承
尽管转发模仿了继承,但是 NSObject
是不混淆二者的。像 respondsToSelector:
和 isKindOfClass:
这样的方法只是在继承链中而不是在转发链中。例如,一个战士对象被询问是否响应 negotiate
消息,
if ( [aWarrior respondsToSelector:@selector(negotiate)] )
...
结果是 NO
,尽管他可以毫无错误的处理这个消息,在这种场景下,将会转发到外交官类。
在大多数情况下,返回 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:
方法实现,用来准确的返回方法描述,这个方法最终将会响应被转发的消息。
- (NSMethodSignature*)methodSignatureForSelector:(SEL)selector
{
NSMethodSignature* signature = [super methodSignatureForSelector:selector];
if (!signature) {
signature = [surrogate methodSignatureForSelector:selector];
}
return signature;
}
你可以考虑把转发程序作为私有方法,可以调动包含 forwardInvocation:
的所以方法。
这时一种高级技术,只有在没有其他解决办法的时候才使用这种解决方案。用它来代替继承是没有意义的,如果你必须使用这些技术,确保你完全理解类的转发行为和你要转发的类。
在这部分提到的方法在 NSObject 中有介绍, 查看NSInvocation来了解更多关于 invokeWithTarget:
的信息。