Autorelease Pool
问题
Autorelease Pool 的作用和原理是什么?和 RunLoop 有什么关系?
答案
什么是 Autorelease Pool
对象被标记为 autorelease 后不会立即释放,而是延迟到 autorelease pool drain(排空)时才调用 release。
底层结构
┌──────────────────────────────────────┐
│ AutoreleasePoolPage │
│ (双向链表 + 栈结构) │
├──────────────────────────────────────┤
│ POOL_BOUNDARY (哨兵) │ ← push 返回的标记
│ obj1 │
│ obj2 │
│ obj3 │
│ next → │ ← 栈顶指针
├──────────────────────────────────────┤
│ ... (4096 字节一页) │
└──────────────────────────────────────┘
- 每个线程有独立的 autorelease pool 栈
@autoreleasepool {}等价于 push + drain- drain 时从栈顶到 POOL_BOUNDARY 逐个 release
与 RunLoop 的关系
系统注册了两个 RunLoop Observer:
- 进入 RunLoop(
kCFRunLoopEntry):创建 pool - 即将休眠/退出(
kCFRunLoopBeforeWaiting/kCFRunLoopExit):释放旧 pool、创建新 pool
何时需要手动 @autoreleasepool
// 场景:循环中创建大量临时对象
for i in 0..<1_000_000 {
// ❌ 所有临时对象等到 RunLoop drain 才释放 → 内存峰值极高
let image = processImage(data[i])
}
for i in 0..<1_000_000 {
// ✅ 每次循环结束释放临时对象 → 内存平稳
autoreleasepool {
let image = processImage(data[i])
}
}
Swift 中的 Autorelease
Swift 原生对象默认不使用 autorelease(编译器优化为 retain/release)。但与 Objective-C/Foundation 交互时(如 NSString、UIImage 等),仍可能产生 autorelease 对象。
常见面试问题
Q1: 子线程需要 Autorelease Pool 吗?
答案:子线程如果使用了 Cocoa API(如 NSString、NSArray 等),需要手动创建 @autoreleasepool。GCD 的线程会自动管理 autorelease pool。pthread 创建的线程如果未手动创建 pool 且使用了 autorelease 对象,会在控制台看到警告。
Q2: ARC 下还需要 @autoreleasepool 吗?
答案:需要。ARC 只是自动管理 retain/release,但某些 ObjC 方法返回的对象仍然是 autorelease 的。在大循环创建临时对象的场景,手动 autoreleasepool 可以控制内存峰值。