MST

星途 面试题库

面试题:Objective-C中id与instancetype在runtime机制下的深度剖析

在Objective-C的runtime机制下,id和instancetype在对象的消息发送、动态方法解析等方面有何不同?从runtime源码层面分析它们的底层实现原理,说明在编写高效、健壮且兼容不同运行环境的代码时,应如何合理选择使用id和instancetype。
31.5万 热度难度
编程语言Objective-C

知识考点

AI 面试

面试题答案

一键面试

1. id 和 instancetype 在消息发送方面的不同

  • id:是一种通用的对象类型,本质是指向 objc_object 结构体的指针,定义为 typedef struct objc_object *id;。在使用 id 进行消息发送时,编译器无法在编译期确定对象的真实类型,只能在运行时根据对象的实际类型来查找对应的方法实现。例如:
id obj = [[NSObject alloc] init];
[obj performSelector:@selector(description)];

这里编译器只知道 obj 是一个对象,但具体类型未知,直到运行时才确定 objNSObject 类型,然后在 NSObject 的方法列表中查找 description 方法的实现。

  • instancetype:主要用于方法返回值类型,它会告知编译器返回对象的实际类型。在消息发送时,编译器可以利用这个信息在编译期进行部分类型检查,并且在运行时能够更高效地进行方法查找。例如,在 NSArray 的类方法 + (instancetype)array 中,返回的 instancetype 明确表示返回的是 NSArray 类型对象。当调用 NSArray *arr = [NSArray array]; 时,编译器知道 arrNSArray 类型,在发送消息 [arr objectAtIndex:0]; 时,编译器可以在编译期检查 NSArray 是否有 objectAtIndex: 方法。

2. id 和 instancetype 在动态方法解析方面的不同

  • id:当使用 id 类型对象接收到无法识别的消息时,动态方法解析机制开始工作。由于 id 类型的不确定性,运行时系统需要在运行时确定对象的类,然后在该类及其继承体系中查找是否可以动态添加方法来处理这个消息。例如:
id unknownObj = [[SomeUnknownClass alloc] init];
[unknownObj someUnknownMethod];

运行时先确定 unknownObj 的类,然后在该类的 + (BOOL)resolveInstanceMethod:(SEL)sel 方法中尝试动态添加 someUnknownMethod 的实现。

  • instancetype:与 id 类似,当 instancetype 类型对象接收到无法识别的消息时,也会触发动态方法解析。但由于 instancetype 明确了对象的大致类型(在编译期已知方法返回类型),在动态方法解析过程中,编译器可以利用这个类型信息进行更优化的处理。例如,如果某个类的类方法返回 instancetype,并且该返回对象接收到无法识别的消息,编译器在解析时可以直接从该类的继承体系开始查找,而不需要像 id 那样先确定具体类型。

3. 从 runtime 源码层面分析底层实现原理

  • id:在 runtime 源码中,objc_object 结构体定义了对象的基本结构,包含一个指向类的指针 isa。当通过 id 发送消息时,runtime 首先通过 isa 找到对象所属的类,然后在类的方法列表及其父类方法列表中查找对应的方法实现。例如,在 objc_msgSend 函数实现中:
OBJC_EXPORT id objc_msgSend(id self, SEL op, ...)
{
    struct objc_super super;
    struct objc_selector *selector;

    super.receiver = self;
    super.super_class = self->isa->super_class;
    selector = sel_getUid(op);

    return ((id (*)(struct objc_super *, struct objc_selector *, ...))objc_msgSendSuper)(&super, selector, ...);
}

这里通过 self(即 id 类型的对象指针)的 isa 找到类,进而查找方法。

  • instancetypeinstancetype 本身并不是一种新的数据类型,它只是一种类型指示符。在编译期,编译器会根据 instancetype 提供的类型信息进行更精确的类型检查和代码生成。在运行时,其底层实现与普通对象类型类似,通过 isa 指针找到类并进行方法查找。但由于编译期的类型信息,运行时可以更高效地处理一些操作,例如方法缓存的利用等。

4. 合理选择使用 id 和 instancetype

  • 使用 id 的场景
    • 当对象类型在编译期无法确定,需要处理不同类型对象的通用操作时,使用 id。例如,在实现一个通用的容器类,需要存储不同类型对象时,容器类的接口可以使用 id 类型。
    • 在处理 NSObject 及其子类的一些通用操作时,id 可以方便地进行类型无关的操作,如 NSArray 中存储不同类型对象,遍历数组时使用 id 来接收数组元素。
  • 使用 instancetype 的场景
    • 在类的构造方法和工厂方法中,返回类型使用 instancetype,可以让编译器知道返回对象的具体类型,提高代码的可读性和安全性。例如 + (instancetype)alloc 方法返回 instancetype,使得调用者能够明确知道返回对象的类型。
    • 当需要在编译期进行类型检查,并且希望运行时能够更高效地进行方法查找和消息发送时,使用 instancetype。比如在一个自定义类的链式调用方法中,返回 instancetype 可以保证每个链式调用的对象类型一致性,编译器也能更好地检查方法调用的正确性。