- 本文会阐述下面几个问题
1、什么是id类型
2、id类型的赋值问题
3、id类型对象在调用方法的时候编译期和运行时的规则
4、NSObject类型与id类型的区别
5、instancetype与id类型的区别
查看源码(源码版本objc4-781.2)
可以看到有两个头文件定义了id类型,我们进一步打开objc.h查看源码,发现有一个预编译宏#if !OBJC_TYPES_DEFINED,意思是没有定义Objective-C再编译里面的内容
#if !OBJC_TYPES_DEFINED
/// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;
/// Represents an instance of a class.
struct objc_object {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
};
/// A pointer to an instance of a class.
typedef struct objc_object *id;
#endif
再打开objc-private.h查看源码,就是它了,嗯~没啥区别,所以id类型是一个结构体指针
typedef struct objc_object *id;
objc_object结构体简略定义如下:
struct objc_object {
private:
isa_t isa;
public:
// ISA() assumes this is NOT a tagged pointer object
Class ISA();
// rawISA() assumes this is NOT a tagged pointer object or a non pointer ISA
Class rawISA();
// getIsa() allows this to be a tagged pointer object
Class getIsa();
...
};
内部定义了isa_t类型的isa联合体,和一系列操作isa的功能函数,有关isa和Class在后续的文章会讨论
id类型为什么能接受任意类型的对象赋值
实际上OC里面任意类型的对象都能用非继承体系的对象赋值,如下四个赋值语句都能顺利通过编译
1、NSString *arr_str = [NSArray new];
2、NSDictionary *arr_dic = [NSArray new];
3、NSArray *arr_arr = [NSArray new];
4、id arr_id = [NSArray new];
只是前三个在编译期会有类型检查,前两个编译器会报出类型不匹配警告,id类型的对象编译器会跳过类型检查
5、[arr_str count];
6、[arr_dic count];
7、[arr_arr count];
8、[arr_id count];
如上,添加四句代码后能否通过编译?答案是6,7可以通过编译,5会报No visible @interface for 'NSString' declares the selector 'count'
,8会报Multiple methods named 'count' found with mismatched result, parameter type or attributes
重点看下第8行代码报的错误,竟然查询到多个同名方法,编译器不知道用哪个,为什么呢?
这是因为id类型的对象,跳过了编译器的类型检查,导致编译器在查询方法符号的时候是在当前文件的可访问范围内查找,我们知道Foundation框架里面的容器类都实现了count方法,所以自然不知道用哪个了,有人会问,为什么不是查询到一个就返回正确的结果呢,这是因为在编译期,编译器会做方法唯一性校验,那么如何骗过编译器的这种校验呢?
我们再添加4行代码
9、[arr_str performSelector:@selector(count)];
10、[arr_dic performSelector:@selector(count)];
11、[arr_arr performSelector:@selector(count)];
12、[arr_id performSelector:@selector(count)];
发现上面方法都可通过编译,原因是performSelector方法同样跳过了编译器的类型检查,所以建议少用这种调用方式,那么上面的四个方法运行时会不会有问题呢,答案是不会有问题,这是因为对象的isa指针指向了该对象的真实类对象NSArray,自然能通过消息查找机制找到的count方法
NSObject类型与id类型的区别
iOS中NSObject是所有类的根类,你会说还有个NSProxy呢,嗯,这个也后续再讨论~,id是什么我们上面已经讨论了,NSObject是一个OC的类,自然在编译期是要进行类型检查的
instancetype与id类型的区别
看小标题就大概知道了吧,我没有在instancetype后面添加类型两个字,原因是instancetype是编译器保留的关键字,只用作函数返回值使用,进行类型检查,判断返回的对象类型是不是当前类自己