【译】runtime编程指南_06消息转发

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 类的实例对象转发消息 negotiateDiplomat 类的实例对象,战士会像外交官一样谈判,似乎他在响应 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: 的信息。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念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

推荐阅读更多精彩内容