第14条:理解“类对象”的用意
对象类型并非在编译期就绑定好了,而是要在运行期查找。
id 类型,它能指代任意的 OC 对象类型。编译器假定它能响应所有消息。
对象类型 id,由于其本身已经是指针了。
在运行期检视对象类型,这一操作也叫做“类型信息查询”(introspection, “内省”),
这个特性内置于 Foundation 框架的 NSObject 协议里,凡是由公共根类(即 NSObjcet 与 NSProxy)继承而来的对象都要遵从此协议。
OC 对象的本质:每一个 OC 对象实例都是指向某块内存数据的指针。
描述 OC 对象所用的数据结构定义在运行期程序库的头文件里,id 类型本身也在定义在这里:
typedef struct objc_object {
Class isa; // 定义了对象所属的类,通常称为“is a”指针。
} *id;
Class 对象也定义在运行期程序库的头文件中:
此结构体存放类的“元数据”(metadata)。
typedef struct objc_class *Class;
struct objc_class {
Class isa; //Class 本身亦为 OC 对象。 //isa 指针所指向的类型是另外一个类,叫做“元类”(metaclass),用来表述类对象本身所具备的元数据。
Class super_class; //本类的超类
const char *name;
long version;
long info;
long instance_size;
struct objc_ivar_list *ivars;
struct objc_method_list **methodLists;
struct objc_cache *cache;
struct objc)protocol_list *protocols;
};
如果对象类型无法在编译期确定,那么就应该使用类型信息查询方法来探知。
用类型信息查询方法来检视类继承体系:(由于对象是动态的,这特性极为重要)
“isMemberOfClass:” 判断出对象是否为某个特定类的实例,
“isKindOfClass:” 判断出对象是否为某类或派生类的实例。
比较类对象是否等同的办法:
使用== 操作符,而不要使用 isEqual:方法
原因在于,类对象是“单例”(在应用程序范围内,每个类的 Class 仅有一个实例)。
某个对象可能会把其收到的所有方法都转发给另外一个对象。这样的对象叫做“代理”,此种对象均以 NSProxy 为根类。
通常情况下,如果在此种代理对象上调用 class 方法,那么返回的是代理对象本身(此类是 NSProxy 的子类),而非接受的代理的对象所属的类。然而,若改用“iKindOfClass:”这样的类型信息查询方法,那么代理对象就会把这条消息转给“接受代理的对象”。
也就是说,这条消息的返回值与直接在接受代理的对象上面查询其类型所得的结果相同。因此,这样查出来的类对象与通过 calss 方法所返回的那个类对象不同,class 方法所返回的“类表示发起代理的对象,而非接受代理的对象”。
所以尽量使用类型信息查询方法来确定对象类型,而不要直接比较类对象,因为某些对象可能实现了消息转发功能。