Objective-C 的运行时系统(Runtime)有两个版本:“Modern” 和 “Legacy”。我们目前使用的 Objective-C 2.0 采用的是现行(Modern)版本的 Runtime 系统,它只能运行于 iOS 和 macOS 10.5 之后的 64 位程序中,而 macOS 较老的 32 位程序仍采用 Objective-C 1.0 早期(Legacy)版本的 Runtime 系统。
在上一篇文章《Objective-C 1.0 中类与对象的定义》中,我们介绍了早期 Objective-C 1.0 中类与对象的定义,本文以最新的 Objective-C Runtime 源码 objc4-750.1.tar.gz 进行阅读和分析 Objective-C 2.0 中类与对象的定义。
需要说明的是,本文只是简单罗列出了 Objective-C 2.0 中类与对象的相关定义,仅作为参考,并没有做深入的分析,因为这方面的文章已经有很多了,详见文末的参考文档。
注:文中的相关示意图均来自霜神(halfrost)的这篇博文,在此不胜感激。
Class 和 id
在 objc-private.h 文件中:
Class被定义为指向struct objc_class的指针;id被定义为指向struct objc_object的指针;
1 | typedef struct objc_class *Class; |
其中,objc_object 结构体的定义如下:
1 | struct objc_object { |
objc_object 结构体用于初始化 Objective-C 中类的实例对象,它包含一个名为 isa 的成员变量,其类型为 union isa_t 共用体,isa_t 中有一个 Class 类型的指针 cls,用于指向实例对象所对应的类,关于 isa 下面会详细分析。
在 objc-runtime-new.h 文件中,objc_class 结构体的定义如下:
1 | struct objc_class : objc_object { |
我们可以发现,objc_class 继承于 objc_object,也就是说,在 Objective-C 中,类本身也是一个对象,其 “isa 指针” 指向对应的“元类”(Meta Class),superclass 指针指向父类,cache 为调用过的方法缓存,而 bits 保存着这个类的属性(成员变量)、实例方法链表、以及遵循的协议等信息,示意图如下:

cache_t
方法缓存的类型 cache_t 的定义如下:
1 | struct cache_t { |

其中,bucket_t *_buckets 是一个散列表,用来存储 Method 方法列表,而 bucket_t 结构体定义如下,包含了一个 unsigned long 类型的 _key 和一个 IMP 类型的 _imp,存储了指针与 IMP 的键值对。IMP 是一个函数指针,指向了一个方法的具体实现。
1 | struct bucket_t { |
cache_t 中另外两个变量 _mask 和 _occupied,它们的类型为 mask_t,定义如下,其实是一个 unsigned int。
1 |
|
_mask 和 _occupied 对应于 vtable:
_mask:分配用来缓存 bucket 的总数。_occupied:表明目前实际占用的缓存 bucket 的个数。
cache的作用主要是对方法调用的性能进行优化。通俗地讲,每当实例对象接收到一个消息时,它不会直接在其isa指向的类(或类的isa指向的父类)的方法列表中遍历查找能够响应消息的方法实现,因为这样效率太低了,而是优先在cache中查找。Runtime 系统会把被调用过的方法存到该类对象的cache中(理论上讲一个方法如果被调用,那么它有可能今后还会被调用),下次查找的时候效率更高。– 摘自这里
class_data_bits_t
objc_class 结构体中最复杂的是 bits 成员变量,其类型为 class_data_bits_t,定义如下:
1 | struct class_data_bits_t { |
从 objc_class 的定义中我们可以发现,objc_class 的 data() 方法直接调用 class_data_bits_t 的 data() 方法,最终是返回 class_rw_t *。
class_rw_t
Objective-C 的类的属性、方法、以及遵循的协议在 2.0 版本之后都放在 class_rw_t 结构体中,其定义如下:
1 | struct class_rw_t { |
class_rw_t 提供了运行时对类拓展的能力,而 class_ro_t 存储的大多是类在编译时就已经确定的信息。二者都存有类的方法、属性(成员变量)、协议等信息,不过存储它们的列表实现方式不同。
class_rw_t 中使用的 method_array_t, property_array_t, protocol_array_t 都继承自 list_array_tt<Element, List>, 它可以不断扩张,因为它可以存储 list 指针,内容有三种:(1)空;(2)一个 entsize_list_tt 指针;(3)entsize_list_tt 指针数组。
class_rw_t 的内容是可以在运行时被动态修改的,可以说运行时对类的拓展大都是存储在这里的。
class_ro_t
通过源码,我们可以知道,objc_class 结构体包含了 class_data_bits_t,class_data_bits_t 存储了 class_rw_t 的指针,而 class_rw_t 结构体又包含 class_ro_t 的指针。
在 class_rw_t 中,class_ro_t 是一个指向常量的指针,存储了编译器决定了的属性、方法和遵守协议。class_ro_t 的定义如下,
1 | struct class_ro_t { |
其中,class_ro_t 里的 method_list_t, ivar_list_t, property_list_t 结构体都继承自 entsize_list_tt<Element, List, FlagMask>,而 protocol_list_t 与前三个不同,它存储的是 protocol_t * 指针列表,实现比较简单。
entsize_list_tt 实现了 non-fragile 特性的数据结构,定义如下:
1 | template <typename Element, typename List, uint32_t FlagMask> |
关于 entsize_list_tt 的分析和 Non Fragile ivars 特性,详见这篇文章。
假如苹果在新版本的 SDK 中向
NSObject类增加了一些内容,NSObject的占据的内存区域会扩大,开发者以前编译出的二进制中的子类就会与新的NSObject内存有重叠部分。Non Fragile ivars特性在编译期会给instanceStart和instanceSize赋值,确定好编译时每个类的所占内存区域起始偏移量和大小,这样只需将子类与基类的这两个变量作对比即可知道子类是否与基类有重叠,如果有,也可知道子类需要挪多少偏移量。
综上,objc_class 中 class_data_bits_t,class_rw_t 和 class_ro_t 的关系如下:

关于 Objective-C 2.0 中方法结构的深入解析,详见这篇文章。
isa
在上一篇文章中,我们介绍了,对象是某一个类的实例,在 Objective-C 中,类也被设计成一个对象,它是其对应的元类(Meta Class)的实例,而元类其实也是一个对象,它的类型是根元类(Root Meta Class),根元类的类型是它自己,它们的继承关系和 isa 指向关系如下图所指示:

此外,类对象和元类对象是全局唯一的,而对象是可以在运行时创建无数个的。在 main 方法执行之前,从 dyld 到 Runtime 加载期间,类对象和元类对象在此时被创建。
在 Objective-C 1.0 中,isa 只是一个指向 Class 的指针,而在 2.0 中,objc_object 中的 isa 是一个 isa_t 类型的共用体(union),在 objc-private.h 文件中定义如下:
1 |
|
其中,ISA_BITFIELD 宏定义了 isa_t 在不同处理器架构(arm64 和 x86_64)下的结构,每个字段的含义如下表:
| 变量名 | 含义 |
|---|---|
| indexed | 0 表示普通的 isa 指针,1 表示使用优化,存储引用计数 |
| has_assoc | 表示该对象是否包含 associated object,如果没有,则析构时会更快 |
| has_cxx_dtor | 表示该对象是否有 C++ 或 ARC 的析构函数,如果没有,则析构时更快 |
| shiftcls | 类的指针 |
| magic | 固定值为 0xd2,用于在调试时分辨对象是否未完成初始化。 |
| weakly_referenced | 表示该对象是否有过 weak 对象,如果没有,则析构时更快 |
| deallocating | 表示该对象是否正在析构 |
| has_sidetable_rc | 表示该对象的引用计数值是否过大无法存储在 isa 指针 |
| extra_rc | 存储引用计数值减一后的结果 |
示意图:

关于 isa 的更多分析和 Tagged Pointer 的概念,详见这篇文章和这篇文章。
SEL
在 objc.h 中,定义了 SEL 为指向 struct objc_selector 的指针:
1 | /// An opaque type that represents a method selector. |
在 objc4 源码中,我们找不到 objc_selector 结构体的具体定义,我们可以把 SEL 理解为就是个映射到方法名的 C 字符串。
此外,不同类中相同名字的方法所对应的方法选择器是相同的,即使方法名字相同而变量类型不同也会导致它们具有相同的方法选择器。
我们可以通过编译器命令 @selector() 或者 Runtime 系统提供的 sel_registerName 函数来获得一个 SEL 类型的方法选择器。
IMP
在 objc.h 中,IMP 的定义如下:
1 | /// A pointer to the function of a method implementation. |
根据宏判断,我们可以知道,在 2.0 中,IMP 为:
1 | typedef void (*IMP)(void /* id, SEL, ... */ ); |
IMP 是一个函数指针,指向方法最终的函数实现,它的参数与 objc_msgSend 函数的参数类型相同,关于 Objective-C 的“方法调用”与“消息转发”,我们这里不再赘述。
Method
在 objc-private.h 中,Method 被定义为一个指向 method_t 结构体的指针:
1 |
|
method_t 结构体在 objc-runtime-new.h 中定义:
1 | struct method_t { |
其中 MethodListIMP 在 objc-ptrauth.h 中声明,可以把它理解为就是 IMP 函数指针:
1 |
|
所以,Method 其实存储了方法名,方法类型和方法实现:
- 方法名类型为
SEL,相同名字的方法即使在不同类中定义,或者参数类型不同,但它们的方法选择器是相同的。 - 方法类型
types是个 char 指针,其实存储着方法的参数类型和返回值类型。 imp指向了方法的函数实现,本质上是一个函数指针。
Ivar
在 objc-private.h 中,Ivar 被定义为一个指向 ivar_t 结构体的指针:
1 |
|
ivar_t 结构体在 objc-runtime-new.h 中定义:
1 | struct ivar_t { |
Ivar 用于表示类中实例变量(成员变量)的类型,ivar_t 中定义了实例变量的名字和类型。
Property
我们知道,@property 用于声明类中的属性,在 Runtime 系统中,objc_property_t 是一个指向objc_property 结构体的指针,在 objc-private.h 中定义:
1 |
|
property_t 结构体在 objc-runtime-new.h 中定义:
1 | struct property_t { |
entsize_list_tt
此外,在 objc-runtime-new.h 中也定义了 method_list_t,ivar_list_t,property_list_t,它们都继承于 entsize_list_tt,如下:
1 | // Two bits of entsize are used for fixup markers. |
entsize_list_tt 的声明如下:
1 | template <typename Element, typename List, uint32_t FlagMask> |
它们将在上述提到的 class_ro_t 的结构体中使用。
Category
在 objc-private.h 中,Category 被定义为一个指向 category_t 结构体的指针:
1 |
|
category_t 结构体在 objc-runtime-new.h 中定义:
1 | struct category_t { |
category_t 存储了分类中可以拓展的实例方法、类方法、协议、实例属性和类属性。在 App 启动时,Runtime 加载完类后,会通过调用 attachCategories 函数进行向类中添加 Category 的工作。原理就是向 class_rw_t 中的 method_array_t, property_array_t, protocol_array_t 数组中分别添加 method_list_t, property_list_t, protocol_list_t 指针。
Protocol
在 objc-runtime-new.h 中定义了 protocol_t 结构体:
1 | struct protocol_t : objc_object { |
protocol_t 继承于 objc_object,其中定义了协议中声明的实例方法,类方法,可选实例方法,可选类方法,实例属性和类属性等。此外,由于 Swift 还支持 Protocol 多继承,所以需要 protocols 数组来做兼容。
protocol_list_t 定义如下:
1 | struct protocol_list_t { |
总结
通过两篇文章的整理,我们可以用如下两张图(来源)来分别表示 Objective-C 1.0 和 2.0 版本中类和对象的定义,及相关数据结构的关系:

