为什么Objective-C的消息转发要设计三个阶段?

栏目: Objective-C · 发布时间: 7年前

内容简介:我们知道,在Objective-C中如果给一个对象发送一条它无法处理的消息,就会进入下图描述的消息转发(Message Forwarding)流程,但是为什么要设计这么复杂的流程呢?消息转发可以分为三个阶段,不同资料中每个阶段的名称不太一样,苹果的官方文档也没有明确指出这三个阶段,所以这里阶段的名称仅供参考。下面我们就通过详细解读每个阶段来回答开篇提出的问题。

我们知道,在Objective-C中如果给一个对象发送一条它无法处理的消息,就会进入下图描述的消息转发(Message Forwarding)流程,但是为什么要设计这么复杂的流程呢?

为什么Objective-C的消息转发要设计三个阶段?

消息转发可以分为三个阶段,不同资料中每个阶段的名称不太一样,苹果的官方文档也没有明确指出这三个阶段,所以这里阶段的名称仅供参考。

下面我们就通过详细解读每个阶段来回答开篇提出的问题。

第一阶段:动态方法解析(Dynamic Method Resolution)

有些情况下,你希望能够为一个方法动态地提供实现。例如,Objective-C中可以将一个属性声明为@dynamic

@dynamic propertyName;
复制代码

这样你就告诉编译器,与这个属性相关联的setter和getter方法会被动态添加。编译器就不会自动为你创建setter和getter以及对应的成员变量(instance variable或叫Ivar)。

你可以通过实现方法 resolveInstanceMethod: 或 resolveClassMethod: 为指定的selector动态添加实现。

一个 Objective-C方法不过是一个C函数,这个函数最少有两个参数——self和_cmd。你可以通过class_addMethod函数把一个函数添加到一个类中。你需要提供类似下面的函数:

void dynamicMethodIMP(id self, SEL _cmd) {
    // implementation ....
}
复制代码

在消息转发的流程中,使用 resolveInstanceMethod: 动态地将一个函数添加为一个类的方法:

@implementation MyClass
+ (BOOL)resolveInstanceMethod:(SEL)aSEL
{
    if (aSEL == @selector(resolveThisMethodDynamically)) {
          class_addMethod([self class], aSEL, (IMP) dynamicMethodIMP, "v@:");
          return YES;
    }
    return [super resolveInstanceMethod:aSEL];
}
@end
复制代码

class_addMethod 最后一个参数叫做types,是一个描述方法的参数类型的字符串。

v代表void,@代表对象或者说id类型,:代表方法选择器SEL。具体参见: Objective-C Runtime Programming Guide->Type Encodings

上面的 dynamicMethodIMP ,返回值是void,两个入参分别是id和SEL,所以描述这个方法的参数类型的字符串就是 "v@:"

这个阶段的意义是为一个类动态提供方法实现。严格来说,还没进入消息转发流程。 respondsToSelector: 和 instancesRespondToSelector: 也会调用 resolveInstanceMethod: 。也就是说,如果 resolveInstanceMethod: 返回了YES,那么 respondsToSelector: 和 instancesRespondToSelector: 都会返回YES。

在CoreData中,有些属性标记为 @dynamic ,这些属性的值背后是通过数据库来更新和获取的,并不需要一个成员变量。所以就会为这些属性的setter和getter方法实现 resolveInstanceMethod: ,返回YES,并通过数据库来设置或者获取该属性的值。

第二阶段:替换消息接收者(快速转发)

如果第一阶段 resolveInstanceMethod: 返回了NO,就会调用 forwardingTargetForSelector: 询问是否把消息转发给另一个对象。这相当于把 objc_msgSend 的第一个参数改为另一个对象,消息的接收者就改变了。

- (id)forwardingTargetForSelector:(SEL)aSelector {
    return someOtherObject;
}
复制代码

第三阶段:完全消息转发机制

如果第二阶段的 forwardingTargetForSelector: 返回了nil,这就进入了所谓完全消息转发的机制。

首先调用 methodSignatureForSelector: 为要转发的消息返回正确的签名:

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    if ([NSStringFromSelector(aSelector) isEqualToString:@"foo"]) {
        return [NSMethodSignature signatureWithObjCTypes:"v@:"];
    }
    return [super methodSignatureForSelector:aSelector];
}
复制代码

返回了正确的签名后,就会调用 forwardInvocation: 将消息转发

- (void)forwardInvocation:(NSInvocation *)anInvocation {
    NSLog(@"forwardInvocation");
    SomeOtherObject *someOtherObject = [SomeOtherObject new];
    if ([someOtherObject respondsToSelector:[anInvocation selector]]) {
        [anInvocation invokeWithTarget:someOtherObject];
    } else {
        [super forwardInvocation:anInvocation];
    }
}
复制代码

上面代码是将消息转发给其他对象,其实这与第二阶段中示例代码做的事情是一样的。区别就在于这个阶段会有一个 NSInvocation 对象。

NSInvocation是一个用来存储和转发消息的对象。它包含了一个Objective-C消息的所有元素:一个target,一个selector,参数和返回值。每个元素都可以被直接设置。

所以不同与第二阶段,在这个阶段你可以:

  • 把消息存储,在你觉得合适的时机转发出去,或者不处理这个消息。
  • 修改消息的target,selector,参数等
  • 多次转发这个消息,转发给多个对象

显然在这个阶段,你可以对一个OC消息做更多的事情。

消息转发与多继承

一个对象通过消息转发来响应一条消息,“看起来像”继承了在其他类定义的方法实现,这就变相实现了多继承。

当然,也许多继承本身就不应该存在。你应该遵循“单一职责”、“高内聚,低耦合”等面向对象设计原则,合理设计类的功能。


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。

Advanced Web Metrics with Google Analytics, 2nd Edition

Advanced Web Metrics with Google Analytics, 2nd Edition

Brian Clifton / Sybex / 2010-3-15 / USD 39.99

Valuable tips and tricks for using the latest version of Google Analytics Packed with insider tips and tricks, this how-to guide is fully revised to cover the latest version of Google Analytics and sh......一起来看看 《Advanced Web Metrics with Google Analytics, 2nd Edition》 这本书的介绍吧!

MD5 加密
MD5 加密

MD5 加密工具

html转js在线工具
html转js在线工具

html转js在线工具

RGB HSV 转换
RGB HSV 转换

RGB HSV 互转工具