2-11.【OC】【内存管理】ARC 下,autoreleasepool {} 的底层结构是什么?

2 阅读2分钟

在 ARC 环境下,@autoreleasepool {} 看起来是一个简单的语法糖,但其底层是一套基于**栈(Stack)**结构的内存管理机制。

当你编写 @autoreleasepool {} 时,编译器会将其转换为一对底层的 C++ 函数调用。


1. 编译器的“变身术”

编译器会将源码转换成类似下面的结构:

C++

// 1. 进入池子
void * context = objc_autoreleasePoolPush();

// --- 你的业务代码 ---

// 2. 退出池子
objc_autoreleasePoolPop(context);

这两个函数实际上是对 AutoreleasePoolPage 类的简单封装。


2. 核心结构:AutoreleasePoolPage

所有的自动释放对象都存储在名为 AutoreleasePoolPage 的 C++ 类中。

  • 物理形态:每一页(Page)占用 4096 字节(虚拟内存的一页大小)。
  • 组织方式:它是以双向链表的形式连接的。当一页存满时,会开辟新的一页。
  • 存储内容:除了头部的成员变量外,剩下的空间全是用来存放对象的内存地址

Page 内部的关键成员:

  • magic:用于校验 Page 结构是否完整。
  • next:指向最新入栈对象后的空闲位置。
  • parent / child:链表的前后指针。
  • thread:该池子所属的线程(AutoreleasePool 是线程私有的)。

3. Push:压栈与“哨兵”

执行 objc_autoreleasePoolPush() 时:

  1. Runtime 会在当前的 Page 中插入一个 nil 值,这个值被称为 POOL_BOUNDARY(边界/哨兵对象)
  2. 返回这个哨兵的内存地址(即上文中的 context)。
  3. 随后在池内创建的所有 autorelease 对象,都会按顺序排列在哨兵地址之后。

4. Pop:清理与销毁

执行 objc_autoreleasePoolPop(context) 时:

  1. Runtime 会根据传入的 context(哨兵地址)找到对应的 Page。
  2. 倒序释放:从 Page 的最新位置(next)开始向后遍历,向每个对象发送一条 release 消息。
  3. 直到遇到哨兵 context 为止。
  4. 内存回垦:移动 next 指针到哨兵位置,并视情况销毁多余的空 Page。

5. 为什么是“栈”结构?

这种设计允许 autoreleasepool 嵌套使用

  • 如果你在一个池子里又开了一个池子,底层只是多插入了一个哨兵值。
  • 当内层池子结束时,它只会清理到自己那个哨兵位置,不会干扰外层池子的对象。

6. ARC 下的特殊优化

在 ARC 下,如果你直接在池子里返回一个对象(例如工厂方法),编译器会使用 objc_autoreleaseReturnValueobjc_retainAutoreleasedReturnValue 这一对函数进行“握手”。

如果这两个函数同时出现: 对象会直接从返回方“传球”给接收方,根本不会进 AutoreleasePoolPage 的栈。这极大地提升了现代 Obj-C 代码的运行效率,减少了对 Page 的读写压力。


总结

  • 数据结构:双向链表组成的栈。
  • 基本单位:4KB 的 AutoreleasePoolPage
  • 关键角色:哨兵值 POOL_BOUNDARY 决定了释放的边界。