消息转发

笔者翻译自iOS Developer Library Message Forwarding

消息转发

向一个对象发送一个它不能处理的消息是错误的。然而,在宣布这个错误前,运行时系统将给这个接收者对象第二次处理消息的机会。

转发

如果你向一个对象发送一个它不能处理的消息,在宣布一个错误前,运行时系统将向该对象发送一条 forwardInvocation: 消息,并带有一个 NSInvocation 对象作为它的唯一参数——NSInvocation 对象封装了原始的消息和它传过来的参数。

你能实现 forwardInvocation: 方法向消息提供一个默认的响应,或者通过一些方式去避免发生错误。就像它的名字的字面意义一样,forwardInvocation: 通常被用作向另一个对象转发消息。

来看看转发的意图,想象接下来的情景:假设,起初,你正在设计一个能够响应 negotiate 消息的对象,并且你希望它的响应能包含另一类对象的响应。你能够很容易地实现它,通过在你实现的 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 类特性,获取更多有关转发和调用的信息。

转发和多继承

转发模仿继承,并且经常给 Objec-C 程序带来多继承的效果。如图所示,一个对象通过转发消息来响应它看起来就像是引入或继承了另一个类中已定义的方法。

Forwarding.jpg

在例图中,一个 Warrior 类的实例向一个 Diplomat 类的实例转发了 negotiate 消息。Warrior 调用 negotiate 看起来就跟 Diplomat 调用一样。它看起来响应了 negotiate 消息,而且实际上,它确实响应了(虽然起作用的其实是 Diplomat 对象)。

转发消息的对象因此“继承“了来自继承层次结构的两个分支(它自己和响应消息的对象)的方法。在上面的例子中,看起来就像是 Warrior 类继承了 Diplomat 和它自己的父类。

转发提供了大部分你通常想通过多继承实现的特性。然而,它们之间也有着重要的区别:多继承将不同的的功能结合到一个单一的对象中,它趋向于单一的,多层次的对象。另一反面,转发将不同的责任分配给不同的对象,它分解问题为小对象,但是用这种方式把这些对象联系起来对消息发送者来说是透明的。

代理对象

转发不仅仅模仿多继承,它也使得开发能代表或“覆盖”更具实质性的对象的轻量级对象成为可能。代理对象代表着其他对象,并且把消息过滤给它们。

Object-C Programming Language 的"Remote Messaging"中讨论的代理(proxy)是这样一个代理对象。它关心把消息转发给远端接收者的管理信息,比如确保参数值通过连接被复制和被恢复等等。但它不尝试去做其他事,它不能复制远端对象的函数功能,而仅仅是给远端对象一个本地地址,通过该地址,它能收到在另一个应用中的消息。

其他类型的代理也是可能的。比如,假设你有一个操作大量数据的对象——或许它要创建一个复杂的图片,或者它要读一个在磁盘上的文件的内容。建立这个对象将会是耗时的,因此你更愿意懒惰地去完成它——当它确实被需要的时候或系统资源临时空闲的时候。但同时,你需要至少一个代表该对象的占位符,以便应用中的其他对象能够正常运行。

在这种情况下,你能够初始化创建它,不需要完全成型的对象,而仅仅为它使用一个轻量级的代理。这个对象能够独立完成一些事情,比如回答关于数据的问题,但是通常它持有一个大对象的地址,并且当时机成熟时,向大对象转发消息。当代理对象的 forwardInvocation: 方法收到一个指向其他对象的消息,它将确保对象是存在的,如果不存在将创建对象。所有大对象的消息都要通过代理。因此,就程序的其他部分而言,代理对象和大对象是一样的。

转发和继承

尽管转发模仿继承,但是 NSObject 类从没有搞混它们。类似 respondsToSelector:isKindOfClass: 的方法仅仅会在继承的层次结构上查找,不会通过转发链。例如:如果 Warrior 对象被询问是否能响应 negotiate 消息:

<pre><code>if ( [aWarrior respondsToSelector:@selector(negotiate)] )
...
</code></pre>
结果为 NO,尽管它能收到 negotiate 消息,并且通过转发给 Diplomat,不会发生错误,也能响应它。

在多数情况下,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 类特性中有详细描述。参考 Foundation 框架说明的 NSInvocation 类特性,了解更多有关 invokeWithTarget: 方法的信息。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,607评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,047评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,496评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,405评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,400评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,479评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,883评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,535评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,743评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,544评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,612评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,309评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,881评论 3 306
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,891评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,136评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,783评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,316评论 2 342

推荐阅读更多精彩内容