跳到主要内容

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:

  1. 进入 RunLoopkCFRunLoopEntry):创建 pool
  2. 即将休眠/退出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 交互时(如 NSStringUIImage 等),仍可能产生 autorelease 对象。


常见面试问题

Q1: 子线程需要 Autorelease Pool 吗?

答案:子线程如果使用了 Cocoa API(如 NSString、NSArray 等),需要手动创建 @autoreleasepool。GCD 的线程会自动管理 autorelease pool。pthread 创建的线程如果未手动创建 pool 且使用了 autorelease 对象,会在控制台看到警告。

Q2: ARC 下还需要 @autoreleasepool 吗?

答案:需要。ARC 只是自动管理 retain/release,但某些 ObjC 方法返回的对象仍然是 autorelease 的。在大循环创建临时对象的场景,手动 autoreleasepool 可以控制内存峰值。

相关链接