在 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() 时:
- Runtime 会在当前的 Page 中插入一个
nil值,这个值被称为 POOL_BOUNDARY(边界/哨兵对象) 。 - 返回这个哨兵的内存地址(即上文中的
context)。 - 随后在池内创建的所有
autorelease对象,都会按顺序排列在哨兵地址之后。
4. Pop:清理与销毁
执行 objc_autoreleasePoolPop(context) 时:
- Runtime 会根据传入的
context(哨兵地址)找到对应的 Page。 - 倒序释放:从 Page 的最新位置(
next)开始向后遍历,向每个对象发送一条release消息。 - 直到遇到哨兵
context为止。 - 内存回垦:移动
next指针到哨兵位置,并视情况销毁多余的空 Page。
5. 为什么是“栈”结构?
这种设计允许 autoreleasepool 嵌套使用。
- 如果你在一个池子里又开了一个池子,底层只是多插入了一个哨兵值。
- 当内层池子结束时,它只会清理到自己那个哨兵位置,不会干扰外层池子的对象。
6. ARC 下的特殊优化
在 ARC 下,如果你直接在池子里返回一个对象(例如工厂方法),编译器会使用 objc_autoreleaseReturnValue 和 objc_retainAutoreleasedReturnValue 这一对函数进行“握手”。
如果这两个函数同时出现: 对象会直接从返回方“传球”给接收方,根本不会进 AutoreleasePoolPage 的栈。这极大地提升了现代 Obj-C 代码的运行效率,减少了对 Page 的读写压力。
总结
- 数据结构:双向链表组成的栈。
- 基本单位:4KB 的
AutoreleasePoolPage。 - 关键角色:哨兵值
POOL_BOUNDARY决定了释放的边界。