第十四条:理解“类对象”的用意
前面介绍了消息转发的机制。然而,消息的接收者究竟是何物?
是对象自身吗?
运行期系统如何知道某个对象的类型呢?
对象类型并非在编译期就绑定好了,而是要在运行期查找。
而且还有个特殊的类型叫做id,它能指代任意的Objective-C对象类型。
一般情况下,应该指明消息接收者的具体类型,这样的话,如果向其发送了无法解读的消息,那么编译器就会产生警告信息。
而类型为id的对象则不然,编译器假定它能响应所有消息。
“在运行期检查对象类型”这一操作也叫做“类型信息查询”(introspection,“内省”)。
这个强大而有用的特性内置于Foundation框架的NSObject协议里,凡是由公共根类(即NSObject与NSProxy)继承来的对象都要遵从此协议。
在程序中不要直接比较对象所属的类,比较明智的做法是调用“类型信息查询方法”。
每个Objective-C对象实例都是指向某块内存数据的指针。所以在声明变量时,类型后面都要跟一个“ * ”
NSString *string = @“some string”;
对于通用的对象类型id,由于其本身已经是指针了,所以我们可以这样写:
id string = @"some string”;
上面两种定义方式语义相同。区别在于,如果声明时指定了具体类型,那么在该类实例上调用其所没有的方法时,编译器会发出警告。
每个对象结构体的首个成员是class类的变量。该变量定义了对象所属的类,通常称为“is a”指针。
typedef struct objc_class *Class;
struct objc_class {
Class isa;
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;
};
此结构体存放类的“元数据”(metadata),例如类的实例实现了几个方法,具备多少个实例变量等信息。
此结构体的首个变量也是is a 指针,这说明Class本身也是Objective-C的对象。
结构体里还有个变量叫做super_class,它定义了本类的超类。
类对象所属的类型(也就是is a指针所指向的类型)是另外一个类,叫做“元类”(metaclass),用来表述类对象本身所具备的元数据。
“类方法”就定义在元类,因为这些方法可以理解成类对象的实例方法。
每个类仅有一个“类对象”,而每个“类对象”仅有一个与之相关的“元类”。
super_class指针确立了继承关系
isa指针描述了实例所属的类
在类继承体系中查询类型的信息
isMemberOfClass: 能够判断出对象是否为某个特定类的实例。
isKindOfClass: 能够判断出对象是否为某类或其派生类的实例。
比较“类对象”是否等同,要用“==”操作符
原因在于,类对象是“单例”(singleton),在应用程序范围内,每个类的class仅有一个实例。
即使能这样,我们也要尽量使用类型信息查询方法
因为类型信息查询可以正确处理那些使用了消息传递机制的对象。
比如说,某个对象可能会把其收到的所有选择子都转发给另外一个对象。
这样的对象叫做“代理”(proxy)。
通常情况下,如果在此种代理上调用class方法,那么返回的是代理对象本身,而非接受的代理的对象所属的类。
若是使用“isKindOfClass:”这样的类型信息查询方法,那么代理对象就会把消息转给“接受代理的对象”。
也就是说,这条消息的返回值与直接在接受代理的对象上面查询其类型所得的结果相同。
class方法:返回的是发起代理的对象的类
isKindOfClass方法:返回的是接受代理的对象的类
【要点】
1.每个实例都有一个指向class对象的指针,用以表明其类型,而这些class对象则构成了类的继承体系。
2.如果对象类型无法在编译期确定,那么就应该适用类型查询方法来探知。
3.尽量使用类型信息查询方法来确定对象类型,而不要直接比较类对象,因为某些对象可能实现了消息转发功能。