面试题答案
一键面试1. id 和 instancetype 在消息发送方面的不同
- id:是一种通用的对象类型,本质是指向
objc_object
结构体的指针,定义为typedef struct objc_object *id;
。在使用id
进行消息发送时,编译器无法在编译期确定对象的真实类型,只能在运行时根据对象的实际类型来查找对应的方法实现。例如:
id obj = [[NSObject alloc] init];
[obj performSelector:@selector(description)];
这里编译器只知道 obj
是一个对象,但具体类型未知,直到运行时才确定 obj
是 NSObject
类型,然后在 NSObject
的方法列表中查找 description
方法的实现。
- instancetype:主要用于方法返回值类型,它会告知编译器返回对象的实际类型。在消息发送时,编译器可以利用这个信息在编译期进行部分类型检查,并且在运行时能够更高效地进行方法查找。例如,在
NSArray
的类方法+ (instancetype)array
中,返回的instancetype
明确表示返回的是NSArray
类型对象。当调用NSArray *arr = [NSArray array];
时,编译器知道arr
是NSArray
类型,在发送消息[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
找到类,进而查找方法。
- instancetype:
instancetype
本身并不是一种新的数据类型,它只是一种类型指示符。在编译期,编译器会根据instancetype
提供的类型信息进行更精确的类型检查和代码生成。在运行时,其底层实现与普通对象类型类似,通过isa
指针找到类并进行方法查找。但由于编译期的类型信息,运行时可以更高效地处理一些操作,例如方法缓存的利用等。
4. 合理选择使用 id 和 instancetype
- 使用 id 的场景:
- 当对象类型在编译期无法确定,需要处理不同类型对象的通用操作时,使用
id
。例如,在实现一个通用的容器类,需要存储不同类型对象时,容器类的接口可以使用id
类型。 - 在处理
NSObject
及其子类的一些通用操作时,id
可以方便地进行类型无关的操作,如NSArray
中存储不同类型对象,遍历数组时使用id
来接收数组元素。
- 当对象类型在编译期无法确定,需要处理不同类型对象的通用操作时,使用
- 使用 instancetype 的场景:
- 在类的构造方法和工厂方法中,返回类型使用
instancetype
,可以让编译器知道返回对象的具体类型,提高代码的可读性和安全性。例如+ (instancetype)alloc
方法返回instancetype
,使得调用者能够明确知道返回对象的类型。 - 当需要在编译期进行类型检查,并且希望运行时能够更高效地进行方法查找和消息发送时,使用
instancetype
。比如在一个自定义类的链式调用方法中,返回instancetype
可以保证每个链式调用的对象类型一致性,编译器也能更好地检查方法调用的正确性。
- 在类的构造方法和工厂方法中,返回类型使用