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

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

​ OC对象的本质：

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

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

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

```objectivec
typedef struct objc_object {
  Class isa,
} *id;
```

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

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

```objectivec
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`，则其继承体系如下：

![类继承体系](https://1957578480-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MQPrkdKRBulXdSdQ_wl%2F-MQPrtQBprnU7IBzECBa%2F-MQPsKbV2zIovnTpFwRC%2F%E7%B1%BB%E7%BB%A7%E6%89%BF%E4%BD%93%E7%B3%BB%402x.png?alt=media\&token=f5cd50da-5dcf-4f93-9f38-65fa8d88b67b)

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

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

* `isMemberOfClass:`：判断对象是否为某个特定类的实例
* `isKindOfClass:`：判断对象是否为某类/其派生类的实例

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

```objectivec
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`对象构成了类的继承体系
* 如果对象类型无法在编译期确定，那么就应该使用类型信息查询方法来探知
* 尽量使用类型信息查询方法来确定对象类型，而不要直接比较类对象，因为某些对象可能实现了消息转发功能

##
