翻译:Objective-C运行时编程指南(五)——消息转发(Message Forwarding)

发送消息到不处理该消息的对象会发生错误。然而,在声明错误之前,运行时系统给接收对象第二次机会处理该消息。

转发

如果发送消息到不处理该消息的对象,在声明错误之前,运行时给该对象发送forwardInvocation: 消息,NSInvocation 对象作为唯一参数。NSInvocation 对象封装原始消息和需要传递的参数。

可以实现 forwardInvocation:方法,提供一个默认消息响应,或者以其他方式避免错误。顾名思义, forwardInvocation:通常用来将消息转发给另一个对象

为了看到转发的范围和目的,想象以下场景:首先假设,你正在设计一个叫做negotiate的对象可以响应消息,你希望它能响应另一个对象的响应。你可以通过传递negotiate消息到你实现的negotiate方法中的另一个对象。

更近一步,假设希望对象精确的响应negotiate 消息,则需要在另一个类中实现。实现这个目标的一个方法是让类继承其他类的方法。然而,它不可能以这种方式安排事情。也许存在充分的理由为什么你的类和实现negotiate 的类在不同分支的继承层次结构中。

即使类不能继承negotiate 方法,仍然可以通过实现一个版本的方法来“借用”它,该方法只是简单的将信息传递给另一个类的实例:

<pre><code>
-(id)negotiate

{

if ( [someOtherObject respondsTo:@selector(negotiate)] )

    return [someOtherObject negotiate];

return self;

}
</pre></code>

这种方式有点麻烦,特别是对象要传递大量消息到另一个对象。你必须实现一个方法来覆盖每个从其他类借来的方法。此外,这种方法不能处理你不知道的情况。例如,在写代码的时候,你想转发所有的消息。这取决于运行时的事件,有可能在将来作为新方法和类实现。

消息提供第二次机会,提供了一个相对不那么特殊的解决方案,该方案是动态的而非静态的。它的工作原理是:当一个对象因为没有一个方法匹配消息中的选择器而无法响应消息时,运行时系统通过发送一个forwardInvocation: 消息通知该对象。每个对象都从NSObject类继承了forwardInvocation: 方法。然而,NSObject版本的方法只是简单的调用doesNotRecognizeSelector:。通过重写NSObject版本,实现自己的版本,可以利用forwardInvocation: 消息提供的机会转发消息到其他对象。

为了转发一条消息,forwardInvocation:方法需要做的是:

确定消息要发送到哪里

发送消息原来的参数到那里

消息可以发送到 invokeWithTarget: 方法:

<pre><code>
-(void)forwardInvocation:(NSInvocation *)anInvocation

{

if ([someOtherObject respondsToSelector:

        [anInvocation selector]])

    [anInvocation invokeWithTarget:someOtherObject];

else

    [super forwardInvocation:anInvocation];

}
</pre></code>

转发消息的返回值返回给原始发送者。所有类型的返回值都可以传递给发送者,包括id,结构,双精度浮点数。

forwardInvocation: 方法可以作为无法识别消息的分发中心,将消息打包给不同的接收者。或者可以作为中转站,发送所有消息到相同的目的地。它可以把一个消息转发给另一个,或简单的“吞咽”一些消息,所有没有响应也没有错误。forwardInvocation:方法还可以合并几个消息到一个响应上。forwardInvocation:做什么是由系统决定的。然而,它提供一个机会,使得转发链接中的链接对象为程序设计提供可能。

注意:方法只有在他们不调用名义上接收者的现有方法的情况下才处理消息。例如,如果你希望你的对象转发negotiate 消息到另一个对象,则对象不能有自己的negotiate 方法。如果是这样,消息永远不会到forwardInvocation:

关于转发和调用的更多信息,参见基础框架引用中NSInvocation 类规范。

转发和多重继承

转发类似继承,可以用来支持Objective-C 编程的多重继承的某些效果。如图5-1所示,一个对象通过转发消息来响应消息,该对象似乎是借或者“继承”另一个类中实现的方法。

图5-1 转发

在这幅图中,Warrior 类的一个实例转发negotiate 消息给Diplomat 类的实例。战士好像作为一个外交官来谈判。好像响应了negotiate消息,事实上,它确实响应了(尽管实际上是外交官来做这些事)。

转发消息的对象“继承”方法,该方法来自继承层次结构的两个分支,该对象自己的分支来响应消息。在上面的例子中,看起来好像Warrior 类继承Diplomat 。

转发提供了大部分多重继承的功能。然而,两者之间有个重要的区别:多重继承在单一对象上结合了不同的功能。它更加强大,多层面对象。另一方面,转发分配单独的职责给不同的对象。它将问题分解为更小的对象,但是以一种方式将对象联合,该方式对消息发送者是透明的。

代理对象

转发不仅模仿多重继承,还可以开发轻量级对象代表或“覆盖”更实质的对象。代理代替其他对象并传送消息给它。

Objective-C 编程语言中“远程消息传递”中描述了该代理。代理负责管理消息转发到远程接受者,确保复制参数值和恢复链接,等等。但不尝试做其他。它不与远程对象的功能重复,只是简单的给远程对象一个本地地址,该地址可以在另一个应用程序中接收消息。

其他类型的代理对象也是可以的。假设,如果有个对象操作大量数据,它也许会创建一个复杂的图片或者读取磁盘上的文件内容。设置该对象可能非常耗时,所以为了简单,只有真正需要的时候或者系统资源暂时闲置时使用。同时,为了保证其他对象在应用程序正常运行,该对象至少需要一个占位符。

在这样的情况下,你可以首先创建,不是成熟的对象,但是时一个轻量级的代理。这个对象可以做些自己的事情,比如回答数据问题,但更多的是为大对象占个位置,当时间到了转发消息给大对象。当代理forwardInvocation: 方法首先接收一条消息传递给另一个对象,它将确保对象存在,如果不存在则创建。所有大对象的消息都是通过代理,因此,对于其余程序而言,代理和大对象是一样的。

转发和继承

尽管转发模仿继承,NSObject类不会混淆两者。像respondsToSelector: 方法和isKindOfClass: 方法只查看继承层次结构,不在转发链上。例如,访问一个战士对象确认是否响应谈判消息。
<pre><code>
if ( [aWarrior respondsToSelector:@selector(negotiate)] )

...

</pre></code>

答案是NO,尽管它可以接收谈判消息,并且没有错误以及通过转发消息给外交官来响应消息(如图5-1)。

在许多情况下,NO是正确答案。但这里也许不是。如果使用转发来设置代理对象或扩展一个类的功能,转发机制必须如同继承一样透明。如果想让你的对象假装它们真正继承它们转发消息的对象的行为,需要重新实现respondsToSelector: 方法和isKindOfClass: 方法,包括转发算法。

<pre><code>
-(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;

}

</pre></code>

除了respondsToSelector: 方法和isKindOfClass: 方法,instancesRespondToSelector:方法也要反应转发算法。如果使用协议,conformsToProtocol:方法需要添加到列表中。同样的,如果一个对象转发它接收到的任何远程消息,它必须有的一个methodSignatureForSelector: 版本,该版本必须可以准确返回描述方法。例如,如果一个对象可以转发消息给它的代理,需要实现如下methodSignatureForSelector: 方法:

<pre><code>
-(NSMethodSignature*)methodSignatureForSelector:(SEL)selector

{

NSMethodSignature* signature = [super methodSignatureForSelector:selector];

if (!signature) {

   signature = [surrogate methodSignatureForSelector:selector];

}

return signature;

}
</pre></code>

可以考虑将转发算法放在私有代码处。

注意:这是一种先进技术,只有在没有其他解决方案可行的情况下才适用。它并不打算取代继承。如果你必须使用这种技术,确保你完全理解转发类和你要转发的类的行为。

在本章中提到的方法在基础框架引用中NSObject类规范中有说明。关于 invokeWithTarget:更多信息,参阅基础框架引用中的NSInvocation 类规范。

官方原文地址:

https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtForwarding.html#//apple_ref/doc/uid/TP40008048-CH105-SW1

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

推荐阅读更多精彩内容