本文介绍 自动释放池(AutoreleasePool) 的原理、底层结构(AutoreleasePoolPage)、与 RunLoop 的协作关系,以及对象何时被批量 release。引用计数基础见 03-引用计数与MRC详解。
自动释放池是什么(简要介绍)
自动释放池(AutoreleasePool) 是用于延迟释放对象的机制:当对象收到 autorelease 时,不会立即让引用计数 -1,而是被加入当前线程的自动释放池;当池被 pop/drain 时,池会对其中所有对象统一发送 release,从而在「某一时刻」批量 -1。在 MRC 下需手写 autorelease;在 ARC 下由编译器在需要时自动插入。主线程的 RunLoop 在每次循环开始会 push 一个池、在休眠或退出前 pop 该池,因此主线程上的 autorelease 对象多在「本次事件处理结束」时被释放。子线程若无 RunLoop,应显式使用 @autoreleasepool { } 控制释放时机,避免临时对象堆积。
一、AutoreleasePool 的作用
1.1 为什么需要
- autorelease 表示「稍后再 release」:不立刻 -1,而是把对象交给当前自动释放池,由池在某一时刻统一对池内对象发送 release。
- 作用:延迟释放,避免在密集创建临时对象的场景下频繁立刻 release,可将多次 release 合并到池 drain 时执行,有利于性能与局部性。
1.2 与 RunLoop 的关系(主线程)
- 主线程 RunLoop 在一次循环中会:
- 进入时:push 一个 AutoreleasePool;
- 休眠/退出前:pop 该池,即对池内所有对象执行 release(drain)。
- 因此,主线程上没有显式
@autoreleasepool时,当前 RunLoop 迭代结束前创建的 autorelease 对象,会在本次迭代末尾被批量释放。
二、@autoreleasepool 语法与底层
2.1 语法
@autoreleasepool {
// 池内创建的 autorelease 对象,在 } 时统一 release
id obj = [SomeObject createObject]; // 若返回 autorelease 对象
}
// 池 pop,obj 收到 release
2.2 底层对应(伪代码)
@autoreleasepool { ... }编译后等价于:- 入口:
objc_autoreleasePoolPush()(入栈一个哨兵/边界); - 出口:
objc_autoreleasePoolPop()(pop 到该边界,对之间加入的对象依次 release)。
- 入口:
2.3 AutoreleasePoolPage(简述)
- 自动释放池由 AutoreleasePoolPage 组成的栈结构实现;每页约 4KB,存若干对象指针。
- push 时可能新开一页或复用当前页;pop 时从栈顶向栈底对每个对象 release,直到遇到对应 push 的边界。
三、释放时机小结
| 场景 | 释放时机 |
|---|---|
| 主线程、无显式 @autoreleasepool | 当前 RunLoop 迭代结束前(休眠/退出时 pop 顶层池) |
| 显式 @autoreleasepool { } | 离开 } 时 pop,池内对象立即被 release |
| 子线程 | 若没有 RunLoop 或未手动加池,需在线程中显式 @autoreleasepool,否则 autorelease 对象可能堆积到线程退出 |
四、流程图:RunLoop 与 AutoreleasePool 协作(主线程)
flowchart LR
subgraph RunLoop 一次迭代
A[进入] --> B[Push Pool]
B --> C[处理事件]
C --> D[休眠/退出前]
D --> E[Pop Pool]
E --> F[池内对象 release]
end
五、应用场景
- 循环中大量创建临时对象:在循环内层包一层
@autoreleasepool { },每轮迭代结束即释放,避免峰值过高。 - 子线程中创建大量 autorelease 对象:在线程入口或循环内使用
@autoreleasepool,避免只依赖线程退出才释放。