一句话开场(总览全局)
Objective-C的Runtime初始化,指的是App启动时,系统加载libobjc.A.dylib动态库,并完成一系列关键的数据结构初始化和方法注册的过程,为之后对象创建、消息发送、方法调用提供支持。
(开场要显得宏观、体系化,不要一上来就死讲细节。)
OC runtime初始化
阶段一:libobjc动态库加载
- 加载libSystem.dylib 调用初始化方法, 最终调用了runtime的_objc_init方法
static void libSystem_initializer(const void* args, int argc, const char* argv[], const char* envp[], const char* apple[], const struct ProgramVars* vars) {
...
libdispatch_init();
...
}
void libdispatch_init(void)
{
_os_object_init(); // 👈 初始化底层对象系统(OSObject)
...
}
void _os_object_init(void)
{
_objc_init(); // 👈 调用 libobjc 中的 runtime 初始化方法
}
_objc_init 做了哪些事情?
-
环境准备
- 初始化锁、环境变量(如 OBJC_DEBUG_* 系列的环境变量)。
- 初始化 Runtime 自身的一些基础数据结构(如类表、选择器表等)。
-
注册回调函数
-
调用 _dyld_objc_notify_register 向 dyld 注册三个回调:
- map_images:每一个镜像被映射进内存时触发。
- load_images:类、分类完成注册后触发(用于 +load 方法调用)。
- unmap_images:镜像被卸载时触发。
-
注册回调函数做了哪些事情?
-
map_images做了哪些事情
- arr_init初始化
void arr_init(void) { AutoreleasePoolPage::init(); SideTablesMap.init(); _objc_associations_init(); } 1. 自动释放池的初始化 2. SideTablesMap 初始化 这个静态全局变量开辟空间 3. AssociationsManager 的初始化 即为全局使用的关联对象表开辟空间-
read_images
它的主要作用是解析 dyld 加载到内存中的 Mach-O 文件中的 Objective-C 元数据(类、分类、协议等),并将它们构建成 Runtime 实际运行所需的对象体系,完成注册和初始化,保证后续消息发送、动态特性能够正常工作。
-
工作时机:read_images 是在 dyld 通知每个镜像加载完成后,通过 __DATA 和 __DATA_CONST 段中的节区(比如 __objc_classlist, __objc_catlist)拿到 ObjC 相关的数据。
-
解析每个类(class_ro_t -> class_t)
-
处理 metaclass(保证 isa 和 superclass 链接正确)
-
将分类(category_t)挂接到原有类的方法列表中(runtime 会合并 category 的方法到类的 method list)
-
处理协议(protocol_t)
-
处理选择器 (selector) 去重(sel_registerName)
-
对某些延迟初始化(如协议附加)做基本登记,但不会马上展开
-
将解析出的类注册到 Runtime 全局哈希表(如 NXMapTable)中,供 objc_msgSend、类查找、动态特性使用。
-
-
注意:如果是 继承自 NSObject 的 Swift 类,Runtime 是支持的。
-
Runtime 会走一个专门的 Swift realize 流程:
-
读取 Swift 的只读数据(Swift 特有的 metadata 格式)
-
把 Swift 的通过关键词@objc修饰的方法 methodlist、property list、protocol list拷贝到 Runtime能认识的结构体里
-
给 class_rw_t 初始化方法列表、属性列表等(让它能被消息发送等机制识别)
-
-
map_images处理完会回调load_images方法
- 加载所有分类
- 将分类中的实例(类)方法,协议,属性添加到类的结构体中
- 将类方法load放到数组中通过函数地址直接调用不走msgSend
- 从父类递归查找load方法
- 调用分类的load方法
注意:load的调用顺序是 父类 -> 子类 -> 分类
swift runtime初始化
swift 类 信息会被编译器编译成元数据 存储在section __swift5_types 中
- NominalTypeDescriptor:描述名义类型(如类、结构体、枚举、协议等)的元数据。
- ProtocolDescriptor:描述协议(Protocol)的元数据。
- Type Metadata:用于描述类型信息(如类型的大小、布局、继承关系等)。
当加载动态库 读取加载mach-o文件的__swift5_types,将数据映射到内存中
举例说明
class MyClass {
var name: String
init(name: String) {
self.name = name
}
}
这个类的元数据会包含以下几部分内容:
-
Class Descriptor:
- 类名、继承关系、方法列表、属性列表等。
-
Type Metadata:
- 类型的大小、对齐要求、是否是类类型、是否是协议等。
-
Vtable(虚表):
- 类的方法表,记录了类的实例方法和类方法。
-
Instance Layout:
- 类的实例内存布局,存储属性的偏移量、大小等信息。
-
Protocol Conformance:
- 如果类遵守了某个协议,协议的描述符也会被记录。
生成时机与过程
-
编译时生成 Type Metadata:
- 编译器会为 MyClass 类生成类型描述符,记录类的大小、内存布局(例如 name 字段的位置)、初始化方法(init(name:))和实例方法(greet())。
- 如果 MyClass 继承自某个类或者遵循某个协议,编译器也会生成相关的继承和协议 conformance 信息。
-
链接时合并 Type Metadata:
- 编译器将 MyClass 类的类型元数据合并到 .o 文件,并通过链接器将其合并到最终的可执行文件中,通常放在 __swift5_types 区段中。
-
运行时加载 Type Metadata:
- 当程序启动时,运行时会加载 __swift5_types 区段,获取所有类型的元数据。
-
首次使用时的加载:
- 当你第一次访问 MyClass(例如,创建实例 let obj = MyClass(name: "John"))时,运行时会通过 Type Metadata 确定如何分配内存、设置字段值、调用方法。并将isa指针指向元数据
swift runtime 和 OC runtime 的区别
-
mach-o中存储内容不同:
- OC是在初始化的时候将内容转成runtime需要的类结构体。创建对象并将isa指向该结构体
- swfit 编译器生成元数据 启动时候加载到内存中即可
两者存储内容不同,swift占用空间比较多,