3-2.【OC】【Runtime】Objective-C 的“类”在 Runtime 层面是什么时候被加载进内存的?

2 阅读2分钟

Objective-C 的“类”在 Runtime 层面的加载是一个严密的流水线作业。它并不是在 main 函数执行时才加载,而是在 App 启动后的 Pre-main 阶段(即 dyld 加载动态库的过程中)完成从“二进制数据”到“内存对象”的转变。

这个过程主要分为 静态映射动态修复(Realization)初始化 三个关键时间节点:


1. 静态映射阶段:_read_images (最早时机)

dyld 将你的 App 二进制文件映射到内存空间后,libobjc_read_images 函数会被触发。这是类第一次在内存中“露脸”。

  • 动作:Runtime 扫描二进制文件的 __DATA 段中的 __objc_classlist 指针数组。
  • 状态:此时类已经分配了地址,但它还是一块“死数据”(class_ro_t)。它虽然在内存里,但你还没法给它发消息,因为它的继承链、方法缓存等都还没准备好。

2. 动态修复阶段:realizeClassWithoutSwift

这是类真正“活过来”的时刻。通常发生在 _read_images 遍历类列表时,或者第一次访问该类时。

  • 动作

    1. 分配空间:为类对象分配 class_rw_t(可读写数据区)。
    2. 继承链联结:将 superclassisa 指针指向对应的父类和元类(Meta Class)。
    3. 分类合并:将该类所有的 Category 方法、属性、协议插入到类的方法列表中。
  • 状态:此时类已经完成了“工业化装配”,可以正常工作了。


3. 初始化阶段:+load+initialize

虽然类结构已经搭好,但类内部的逻辑激活有两个重要的生命周期:

  • +load (Pre-main 阶段)

    当类被 Realize(修复)完成后,libobjc 会在 main 函数之前统一调用所有类的 +load

    • 特点:此时类刚刚加载进内存,环境还比较原始。
  • +initialize (运行时)

    这才是“懒加载”的体现。只有当你的代码第一次向该类发送消息(比如 [MyClass alloc])时,Runtime 才会调用这个方法。


总结:类加载的时间线

启动阶段触发函数类的状态
dyld 加载镜像_read_images发现类,建立地址映射(静态数据)
Pre-main 阶段realizeClass内存结构补全,合并分类,连接父类
Pre-main 阶段call_load_methods执行 +load,类已就绪
Main 之后(首次使用)lookUpImpOrForward执行 +initialize(懒加载初始化)

💡 一个有趣的细节

如果一个类在整个 App 运行过程中从未被使用过(且没有 +load 方法),为了节省内存,Runtime 可能会推迟它的 Realize 过程。这种**“按需装配”**的机制保证了即使项目中有上万个类,启动速度也不会受到毁灭性的影响。