(原文出处:Objective-C Internals | Always Processing)
简要介绍Objective-C运行时源代码,重点是对象和类类型的定义、继承的实现方式、根类的特殊情况,以及与元类查找相关的内容。
前一篇文章探讨了Objective-C的类架构,并为类层次结构绘制了一个对象图。在这里,我们将在这些概念的基础上,通过检查类对象图的实现(类、超类和元类)来继续探讨。
让我们从一些关键类型的公共定义开始。在Objective-C中,Class类型代表任何类类型,id类型代表任何类的实例。Objective-C运行时头文件objc.h定义了这些类型:
arduino
/// 一个不透明类型表示了一个 Objective-C 类。
typedef struct objc_class *Class;
/// 类的实例。
struct objc_object {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
};
/// 指向类实例的指针。
typedef struct objc_object *id;
如前一篇文章所述,Objective-C类也是对象,但在公共类型定义中并没有这种关系。然而,如果我们查看内部类型定义,我们确实可以看到这种关系。
首先,objc-private.h包含了实际的objc_object定义。虽然内部定义有许多C++的非虚函数,但它的唯一成员变量isa_storage对应于(已弃用的)isa实例变量。(我不知道为什么内部类型是字符数组,猜测这可能是为了防止在字段的各种重载中意外直接使用。我会在本文中更详细地讨论isa字段。)
c
struct objc_object {
char isa_storage[sizeof(isa_t)];
};
接下来,objc-runtime-new.h包含了objc_class数据结构的定义。它有几个自己的成员变量,与objc_object一样,它也有许多C++的非虚函数。
arduino
struct objc_class : objc_object {
// Class ISA;
Class superclass;
cache_t cache; // 以前是缓存指针和虚函数表
class_data_bits_t bits; // class_rw_t * 加上自定义的rr/alloc标志
};
没有特定于元类的类型;元类只是一个类,尽管objc_class类型具有用于识别和操作元类的内部支持。
在这里,我们看到objc_class派生自objc_object,因此继承了isa字段。因此,类对象的实现与任何其他对象类型相同。接下来是superclass字段,它指向父类对象,如果有的话。(cache和bits不是本文重点,我们之后再探讨。)
这就是构建Objective-C类图所需的全部内容:两个数据结构(objc_object和objc_class)和两个字段(isa和superclass)!
objc_class成员函数
接下来,我们看一看objc_class中的成员函数,以了解架构图中边的实现。

csharp
// 根类
bool isRootClass() {
return getSuperclass() == nil;
}
如果一个类没有超类,那么它是一个根类。但在实践中,这是不常见的,因为几乎所有的Objective-C对象都是从NSObject(或在罕见情况下是NSProxy)派生的。因此,根元类不是根类。
csharp
// 根元类
bool isRootMetaclass() {
return ISA() == (Class)this;
}
根元类具有自指的isa指针,这是运行时标识根元类的方式。据我所知,这是类图中唯一的循环。
csharp
// 元类标识
bool isMetaClass() const {
return cache.getBit(FAST_CACHE_META);
}
// 与isMetaClass相同,但也适用于未实现的类
bool isMetaClassMaybeUnrealized() {
if (isStubClass())
return false;
return bits.flags() & RW_META;
}
由编译器发出的一个位标志标识一个元类实例,这是将元类实例与类实例区分开的主要特征。
未实现的类,包括存根类,在Objective-C内部探秘4: 未实现的类(和桥接)
一文中有更详细的描述。
kotlin
// 获取元类
// 当这是元类时,与this->ISA不同
Class getMeta() {
if (isMetaClassMaybeUnrealized()) return (Class)this;
else return this->ISA();
}
从某个类实例检索元类时,有必要检查该实例是否为元类。如果是元类,则返回它自己。否则,类实例通过其isa指针返回元类。
编译器输出
objc_class数据结构是Objective-C ABI的一部分,这意味着其大小和字段布局的详细信息已知于第三方程序,它们将这些信息编码到其可执行二进制文件中。通过运行clang -S MyObject.m为上述MyObject.m文件生成汇编文件,将会生成包含以下片段(和更多内容)的汇编文件。
bash
.section __DATA,__objc_data
_OBJC_CLASS_$_MyObject:
.quad _OBJC_METACLASS_$_MyObject
.quad _OBJC_CLASS_$_NSObject
.quad __objc_empty_cache
.quad 0
.quad __OBJC_CLASS_RO_$_MyObject
_OBJC_METACLASS_$_MyObject:
.quad _OBJC_METACLASS_$_NSObject
.quad _OBJC_METACLASS_$_NSObject
.quad __objc_empty_cache
.quad 0
.quad __OBJC_METACLASS_RO_$_MyObject
在这里,我们可以看到编译器生成的代码与我们从前一篇文章的架构图中得出的观察一致:
MyClass类对象有:
- 一个isa变量,指向MyClass元类。
- 一个super变量,指向NSObject类对象。
MyClass元类有:
- 一个isa变量,指向NSObject(根对象)元类。
- 一个super变量,指向NSObject元类。
(如上所述,cache和bits字段将在未来的文章中进行探讨。)