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

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

内容简介:我们知道,在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消息做更多的事情。

消息转发与多继承

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

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


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

查看所有标签

猜你喜欢:

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

Python带我起飞

Python带我起飞

李金洪 / 电子工业出版社 / 2018-6 / 79

《Python带我起飞——入门、进阶、商业实战》针对Python 3.5 以上版本,采用“理论+实践”的形式编写,通过大量的实例(共42 个),全面而深入地讲解“Python 基础语法”和“Python 项目应用”两方面内容。书中的实例具有很强的实用性,如对医疗影像数据进行分析、制作爬虫获取股票信息、自动化实例、从一组看似混乱的数据中找出规律、制作人脸识别系统等。 《Python带我起飞——......一起来看看 《Python带我起飞》 这本书的介绍吧!

图片转BASE64编码
图片转BASE64编码

在线图片转Base64编码工具

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具