美文网首页MacOS开发 技术集锦iOS&Mac
Message Forwarding(消息转发)

Message Forwarding(消息转发)

作者: ADreamClusive | 来源:发表于2018-04-28 14:17 被阅读7次

    向不处理该消息的对象发送消息将会报错。然而,在报错之前,运行时系统给接收对象提供了第二次处理消息的机会。

    1 转发

    如果你将消息发送给一个不处理它的对象,运行时系统会在报错之前,给该对象发送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];

    }

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

    forwardInvocation: 方法充当一个未识别消息的分发中心,负责将消息分发到不同的接收者。或者它是一个传送站,把所有的消息发送到同一个目的地。它可以将一个消息转化为另一个消息,或者简单地“吞咽”一些消息,这样就没有响应,也没有错误。forwardInvocation: 方法也可以将多个消息合并为单个调用。forwardInvocation: 的作用取决于实现者。然而,它为转发链中链接对象提供的机会为程序设计打开了可能性。

    注:forwardInvocation:方法只有在他们不能调用接受者中存在的方法时才处理消息。例如,如果你希望你的对象转发negotiate消息给另一个对象,它本身就不能有negotiate方法。如果他有这个方法,消息就不会到达forwardInvocation:。

    有关转发和调用的更多信息,请参考Foundation框架参考中的NSInvocation类规范。

    2 转发和多重继承

    转发模仿继承,并可用于将多继承的影响引入OC程序。如图5-1所示,一个对象通过转发来响应一个消息,就像借用或“继承”自另一个类中定义的方法实现。

    在这个插图中,Warrior类的一个实例将negotiate消息转发到了Diplomat类的一个实例。战士(Warrior)看起来像外交官(Diplomat)一样谈判(negotiate)。它似乎会对negotiate消息作出回应,并且事实上它确实做出了回应(尽管事实上是一个外交官在做这项工作)。

    因此,转发消息的对象从继承层次结构中的两个分支“继承”方法:他自己的分支和响应消息的对象的分支。在上边的例子中,就好像战士继承了外交官和他自己的超类(父类)。

    转发提供给了您通常希望从多重继承中获得的大多数特征。然而,两者之间一个重要的区别是:多重继承组合了单个对象的不同的能力。它倾向于大的、多方面的物体。另一方面,转发将不同的责任分派给不同的对象。它将问题分解为更小的对象,但以一种透明的方式将这些对象与消息发送者关联起来。

    3 代理对象

    转发不仅模仿多继承,他还可以开发能够表示或“覆盖”更多实质对象的轻量级对象。代理可以为其他类过滤消息。

    在OC语言中“Remote Messaging”中讨论的代理是这样的代理。该代理关心转发消息到远程接收器的管理细节,以确保参数值在连接上被复制和索引,等等。但是,它并不尝试做其他事情;不复制远程对象的功能,而是简单的给远程对象一个可以用于在另一个程序中接收消息的本地地址。

    其他类型的代理对象也是可以的。例如,假设你有一个可以操纵大量数据的对象--可能它创建一个复杂的图像或者读取磁盘上一个文件的内容。设置这个对象是耗时的,所以你更加倾向于懒加载--当真正需要的时候或者当系统资源暂时空闲的时候。同时,为了使程序中其他对象正常工作,至少需要为该对象设置一个占位符。

    在这种情况下,你可以初始创建一个轻量级的代理,而不是一个完整的对象。这个对象可以自己做一些事情,比如回答关于数据的问题,但是大多数情况下它只是为较大的对象保留一个位置,当时间到来时,向它转发消息。当代理的forwardInvocation:方法收到去往另一个对象的消息时,它将确保对象是存在的,如果对象不存在则创建该对象。对于较大对象的所有消息都经过代理,因此,就程序的其余部分而言,代理和较大对象是一样的。

    4 转发与继承

    虽然转发模仿继承的一些行为,但是NSObject类从来不会混淆这两者。像respondsToSelector:和isKindOfClass:方法,只在继承层次中查找,绝不会在转发链中查找。比如,一个战士对象被询问是否响应negotiate消息,

    if ( [aWarrior respondsToSelector:@selector(negotiate)] )

        ...

    答案是NO,即使它可以接受negotiate消息而没有发生错误,并且在某种意义上将消息转发给外交官来响应它们。(如图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类规范。

    相关文章

      网友评论

        本文标题:Message Forwarding(消息转发)

        本文链接:https://www.haomeiwen.com/subject/cssglftx.html