Day5-Objective-C Runtime 基础结构

YVTU

Objective-C Runtime 基础结构

Objective-C 运行时系统(Runtime)是 Objective-C 语言的核心部分,它在程序运行期间负责管理类、对象、方法调用等行为。

主要的结构体和组件包括:

1. objc_class(类结构体)

1
2
3
4
5
6
7
8
9
10
11
12
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; // 协议列表
};

注意:现代 Runtime 中,结构体已经变化了,上述是经典(早期)的描述,新的版本更复杂且高度封装。


2. objc_object(对象结构体)

1
2
3
struct objc_object {
Class isa; // 指向对象所属的类
};
  • 每一个对象底层其实就是一个指针,最重要的成员就是 isa
  • isa 既可以指向 Class,也可以指向 Meta-Class(元类)。

3. objc_method(方法结构体)

1
2
3
4
5
struct objc_method {
SEL name; // 方法选择器(Selector)
char *types; // 方法的参数和返回值类型编码
IMP imp; // 方法实现的函数指针
};
  • SEL 是方法名的唯一标识。
  • IMP 是函数指针,本质上就是 C 函数的地址。

4. objc_ivar(成员变量结构体)

1
2
3
4
5
struct objc_ivar {
char *name; // 变量名
char *typeEncoding; // 类型编码
int offset; // 变量在对象内存中的偏移量
};
  • 成员变量的信息,包括名字、类型和内存位置。

5. objc_property(属性结构体)

在现代 Runtime 里面,属性也有自己的结构:

1
2
3
4
typedef struct {
const char *name; // 属性名
const char *attributes; // 属性修饰列表(例如:T@"NSString",C,N,V_name)
} objc_property_t;
  • 属性其实是成员变量 + 一些特定特性(getter/setter规则)。

小总结

组件 作用
objc_object 所有对象的基础
objc_class 类对象描述结构
objc_method 方法的定义
objc_ivar 成员变量定义
objc_property 属性定义

进阶补充

现代 Objective-C Runtime(如 iOS 14+),这些结构体实际上被进一步封装优化,比如:

  • 类使用 objc_classobjc_data_bits_t(联合体)分离了缓存、只读数据等。
  • methodList 也使用更紧凑的链表存储。
  • isa 指针通过 isa 指针优化(ISA指针打包) 包含了更多信息,比如引用计数、是否有C++析构函数等。

现代 Objective-C Runtime 的优化

  1. objc_class 和 objc_data_bits_t 的分离

早期(传统)的结构:

在早期的 Objective-C Runtime 中,类对象(objc_class)包含了所有关于类的描述信息,包括:

  • isa 指针(指向元类)
  • super_class(父类指针)
  • 成员变量、方法列表、属性等

这种结构虽然简单,但随着 iOS 设备性能的提高,运行时系统也需要对这些数据结构进行优化。

现代优化:

在 iOS 14 及之后的版本中,Objective-C Runtime 通过 分离 类的各个组成部分,提高了性能和内存效率。

objc_class 现在主要包含 元类的引用,而其它数据(如方法列表、属性、成员变量等)被移到了 objc_data_bits_t 结构中。这种方式的好处是:

  • 缓存友好:类的元数据可以被存储在内存的紧凑区域,提高了缓存命中率。
  • 分离存储:类数据和方法数据分开存储,避免了冗余的内存占用。
  • 增强的灵活性:可以在不同的硬件架构下对这些数据进行不同的布局,以便更好地适配不同的内存和处理器特性。

例子:

objc_class 结构变得非常简洁:

1
2
3
4
5
6
7
struct objc_class {
Class isa; // 指向元类
Class super_class; // 父类
const char *name; // 类名
...
objc_data_bits_t data; // 类的其它元数据,如方法、属性、缓存等
};

而 objc_data_bits_t 则包含了实际的数据部分:

1
2
3
4
5
6
7
8
9
union objc_data_bits_t {
struct {
method_list_t *methodList; // 方法列表
ivar_list_t *ivarList; // 成员变量列表
property_list_t *propertyList; // 属性列表
cache_t cache; // 方法缓存
};
uintptr_t raw; // 原始内存表示
};

  1. methodList 存储的优化

早期存储:

传统的 methodList 是基于链表(Linked List)存储的。这种方式虽然简单,但查找方法时效率较低,尤其是在类层级很深或者方法很多的情况下。

现代优化:

在现代的 Runtime 系统中,methodList 不再是单纯的链表。它现在采用了一种 紧凑的链表结构,并且结合了 哈希表(Hash Table) 或者 二叉搜索树(Binary Search Tree),大大提高了方法查找的效率。

  • 哈希表:通过哈希值来存储方法,查找操作时间复杂度为 O(1)。
  • 二叉搜索树:如果方法列表较大,可能使用树结构来提高查找性能(特别是方法名排序后)。

这种优化使得对于频繁调用的方法,能够以更高的效率进行查找。

优化的好处:

  • 方法查找速度更快。
  • 降低了内存占用,尤其在方法很多的类中表现尤为突出。
  1. isa 指针优化(ISA指针打包)

早期 isa 指针:

早期的 isa 指针实际上是一个单纯的指针,它指向当前对象所属的类。当我们需要判断一个对象的类型时,必须通过这个指针进行查找。这个过程虽然有效,但随着内存和处理器架构的变化,其效率并没有得到最优的利用。

现代优化:

iOS 14 及之后的版本引入了 ISA 指针打包(ISA pointer packing) 的概念,使得 isa 指针变得更加高效。具体来说:

  • 压缩指针:isa 不仅仅是一个指针,它包含了更多的信息,包括:
  • 引用计数:可以通过 isa 字段直接获取对象的引用计数(而不需要额外的字段)。
  • C++ 析构函数支持:对于 C++ 对象,isa 可以包含一个标记,表明该对象是否有 C++ 析构函数需要调用。
  • 指针压缩:针对 64 位设备,指针已经被压缩,节省了内存空间。
  • 指针布局:通过优化指针的布局,可以减少内存访问的延迟。例如,将常用的标记和信息放在 isa 中,减少了缓存缺失的概率。

结构变化:

1
2
3
struct objc_object {
Class isa; // 压缩后的 isa 指针,包含更多信息
};

这种优化使得:
• 内存使用:减少了内存占用。
• 效率提升:访问对象所属类时的效率大幅提高。


总结

在现代 Objective-C Runtime 中,结构体的优化和指针的打包大大提升了系统的性能,尤其是在内存占用和方法查找速度上。重点优化如下:
• objc_class 和 objc_data_bits_t 的分离:提高了内存布局的紧凑性和效率。
• methodList 的优化:结合哈希表和二叉搜索树,提高了方法查找的效率。
• isa 指针优化:通过指针打包,减少了内存占用,并加速了对象的类型查找和引用计数管理。

这些改进的核心目的是:提升性能、减少内存占用,特别是在 iOS 设备上,确保运行时系统能够在有限的硬件资源上高效运行。