Objective-C 运行时编程指南 之 Message Forwarding

发送消息到一个不处理该消息的对象是错误的。然而,在报告这个错误之前,运行时系统给予接收对象第二次机会处理这个消息。

5.1 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 类的说明。

5.2 Forwarding and Multiple Inheritance 转发和多重继承

转发与继承类似,可以用于给Objective-C程序提供某些多重继承的效果。如图5-1所示,对象通过转发响应一个消息好像借或“继承”了定义在另一个类中的方法实现。

Figure 5-1 Forwarding
Forwarding

在这幅插图中,Warrior类的实例转发了 negotiate 消息的给Diplomat类的实例。Warrior将会像Diplomat一样谈判。它似乎响应了 negotiate 消息,并能响应所有实际上的目的(尽管这确实是Diplomat在做这项工作)。

转发了消息的对象就这样从继承层级的两个分支“继承”了方法——它自己的分支和响应这个消息的对象的分支。在上面的例子中,就好像Warrior类继承自Diplomat类,也继承自它自己的父类。

转发提供了你通常想要从多重继承获得的大部分特性。但是,二者有一个重要的不同:多重继承将不同的功能结合在一个单独的对象中。它趋向于形成大的,多功能的对象。而转发则是将责任分配给不同的对象。它将问题分解成较小的对象,但是将这些对象以透明的方式关联到消息的发送者。

5.3 Surrogate Objects 替代者对象

转发不仅可以模拟多重继承,它也使得有可能开发轻量级对象来表示或“覆盖”更丰富的对象。替代者就为另一个对象而存在并向它传输消息。

The Objective-C Programming Language 》的《Remote Messaging》中讨论的代理就是这样一种替代者。代理负责转发消息给远程接收者的具体细节,确保参数值已经复制并通过连接检索,等等。但是它不会试图做其他事情;它不复制远程对象的功能,而只是简单的给远程对象一个本地地址,一个可以在另一个应用中接收消息的地方。

其他形式的替代者对象也是可能的。例如,假设你有一个对象管理了大量数据——它可能创建了一个复杂的图像或者读取了一个磁盘文件的内容。构建这个对象可能非常耗时,因此你倾向于懒加载——当它真的需要时或者当系统资源暂时闲置时才加载。同时,你至少需要一个该对象的占位符,以便程序中的其他对象能够正确的运转。

在这种情况下,你可以在起初为它创建一个轻量级的替代者,而不是完整的对象。这个对象能够靠自己完成一些工作,例如回答关于数据的问题,但大部分时候它只是为更大的对象持有一片空间,并且在需要时转发消息过去。当替代者的 forwardInvocation: 方法第一次收到指定给另一个对象的消息,它会确保那个对象存在,如果不存在就创建它。所有给更大对象的消息都通过替代者传递,因此,只要关注程序的其他地方,替代者和更大的对象就是一样的。

5.4 Forwarding and Inheritance 转发和继承

尽管转发模拟了继承, 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 类说明。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值