14、理解“类对象”的用意

“在运行期检视对象类型”这一操作也叫做“类型信息查询”(introspection,“内省”),这一个强大而有用的特性内置于Foundation框架的NSObject协议里,凡是由公共根类(common root class,即NSObjectNSProxy)继承而来的对象都要遵从此协议。在程序中不要直接比较对象所属的类,明智的做法是调用“类型信息查询方法”。

​ OC对象的本质:

// 每一个OC对象实例都是指向某块内存数据的指针。所以在声明变量时,类型后面都要跟一个`*`字符
NSString *pointerVariable = @"Hello world";
// 对于通用对象类型`id`,由于其本身已经是指针了,所以我们能够这样写
id genericTypeString = @"Hello world"

上面两种定义方式区别在于:
  第一种声明时指定了具体类型,当调用其没有的方法时,编译器会发出警告⚠️。

​ OC对象所用的数据结构定义在运行期程序库的头文件里,id类型本身也定义在这里:

typedef struct objc_object {
  Class isa,
} *id;

​ 由此可见,每个对象结构体的收个成员是Class类的变量。该变量定义了对象所属的类,通常称为“is a”指针。(如:string对象“是一个”(is a)NSString,所以其“is a”指针就指向NSString

Class对象也定义在运行期程序库的都文件中:

typedef struct objc_class *Class;
struct objc_class { // 吃结构体存放类的“元数据”(metadata)
  Class isa; // 说明Class本身也是OC对象
  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_cahce *cache;
  struct objc_protocol_list *protocols;
}

​ 类对象所属的类型(即isa指针指向的类型)是另外一个类,叫“元类”(metaclass),用来表述类本身所剧本的元数据。“类方法”就定义于此处,这些方法可以理解为类对象的实例方法。每个类仅有一个“类对象”,每个“类对象”仅有一个与之相关的“元类”。

​ 假如有一个名为SomeClass的子类继承自NSObject,则其继承体系如下:

类继承体系

super_class指针确立了继承关系,而isa指针描述了实例所属的类。通过这张布局关系图即可执行“类信息查询”。可以查出对象是否能响应某个选择子,是否遵从某项协议,看出此对象位于“类继承体系”(class hierarchy)的哪一部分。

在类继承体系中查询类型信息:

  • isMemberOfClass::判断对象是否为某个特定类的实例

  • isKindOfClass::判断对象是否为某类/其派生类的实例

使用isa指针获取对象所属的类,然后通过super_class指针在继承体系中游走。由于OC对象是动态的,所以此特性显得极为重要。从collection中取出的对象类型通常是id的,可以使用类型信息查询方法。如:

NSMutableString *str = [NSMutableString new];
for (id obj in array) {
  if ([obj isKindOfClass:[NSString class]]) {
    [str appendFormat:@"%@", obj];
  } else if ([obj isKindOfClass:[NSNumber class]]) {
    [str appendFormat:@"%d", [obj intValue]];
  } else {
    // ...
  }
}

​ 不应该直接比较两个类对象是否等同,如:某个对象可能会把收到的所有选择子都转发给另外一个对象。这样的对象叫做“代理”(proxy),通常继承自NSProxy。通常在此对象上调用class方法,返回的是代理对象本身(NSProxy的子类),而非接受代理的对象所属的类。(用isKindOfClass:这样的类型信息查询方法,代理对象会转发给“接受代理的对象”;而用class返回的是发起代理的对象,而非“接受代理的对象”)。

要点:

  • 每个实例都有一个指向Class对象的指针,用以表明其类型,这些Class对象构成了类的继承体系

  • 如果对象类型无法在编译期确定,那么就应该使用类型信息查询方法来探知

  • 尽量使用类型信息查询方法来确定对象类型,而不要直接比较类对象,因为某些对象可能实现了消息转发功能

Last updated

Was this helpful?