“都已经是 ARC 时代了,为什么还要使用 @autoreleasepool?”
这是我经常被后辈问到的问题。明明是在某处见过的关键字,但要立即回答什么时候以及为什么使用它,却意外地不容易。
今天就从@autoreleasepool是什么开始,讲清楚实际开发中什么时候该使用它。
先说结论。
@autoreleasepool是一个代码块,可以在你指定的时间,提前释放那些“预约稍后释放的对象”。在循环中大量产生临时对象时,防止内存暴增是它最典型的用途。
下面逐一说明。
什么是自动释放池?
先从背景概念开始。
Objective-C 很早以前就提供了autorelease这一机制。它会把对象预约为“现在先不要,过一会儿再 release”。
这些已预约对象排队等待的列表,就是 autorelease pool。
当 pool 被 drain 时,列表中的对象会一次性收到 release。
那么,这个 pool 什么时候会被清空呢?
在 iOS App 中,主运行循环会自动管理它。每次完成一个周期——处理触摸事件,再刷新画面——都会彻底清空 pool。
所以平时我们无需操心。
问题会在循环中爆发
不过,“每次周期结束时”这个条件,在某些情况下反而会成为问题。
也就是在一次运行循环执行期间,积累了大量临时对象。
例如,假设在一个循环中处理数千张照片。
for (int i = 0; i < 5000; i++) {
// 每次迭代都会临时创建一个大型图像对象
UIImage *image = [self loadAndResizeImage:i];
[self saveThumbnail:image];
}
循环运行期间,运行循环没有机会清空 pool。
因此,仅被标记为等待释放的临时对象,会完整积累相当于 5000 张照片的内存。
内存图像像山一样飙升,严重时系统会强制终止 App。
用 @autoreleasepool 削平这座山
解决方法很简单:在循环内部创建专用的 pool,每次迭代后手动清空。
for (int i = 0; i < 5000; i++) {
@autoreleasepool {
UIImage *image = [self loadAndResizeImage:i];
[self saveThumbnail:image];
} // 代码块结束的瞬间,临时对象就会立即释放
}
@autoreleasepool代码块关闭时,其中创建的临时对象会立即清理。
不再是积累相当于 5000 张照片的内存后一次性释放,而是每次积累一张、释放一张。
原本山形的内存图像会变成平缓的锯齿形。
在 Swift 中这样使用
你可能会说:“我只用 Swift。”Swift 也有完全相同的工具。
就是autoreleasepool函数。
for i in 0..<5000 {
autoreleasepool {
let image = loadAndResizeImage(i)
saveThumbnail(image)
}
}
有一点需要注意。
纯 Swift 对象大多不会经过 autorelease pool,作用域结束时就会立即释放。
这个工具真正发挥作用的场景,是重复调用UIImage、Data(contentsOf:)、NSData等基于 Objective-C 的 Foundation・UIKit API。
因为现在仍有许多 API 会在内部创建并返回 autorelease 对象。
这些时候就该使用
总结来说,需要使用@autoreleasepool的情况包括以下几种。
- 在循环中大量创建图像、文件、字符串等大型临时对象时
- 在后台线程中没有运行循环,却需要长时间执行任务时
- 在命令行工具等没有运行循环的环境中使用 Objective-C 对象时
反过来,普通 UI 代码和一般业务逻辑通常不需要特意使用。主运行循环已经管理得很好。
也不建议不做测量就习惯性地包裹代码。应先在 Instruments 或 Xcode 内存图像中确认实际出现峰值,再决定使用。
常见问题(Q&A)
Q. ARC 会自动处理,为什么还要我管理 pool?
ARC(Automatic Reference Counting,自动引用计数)只会替你插入 retain 和 release,并不会改变 autorelease pool 何时清空。提前清空 pool 的“时机”,仍然是开发者的职责。
Q. main 函数中的 @autoreleasepool 是什么?
打开 Objective-C 项目的 main.m,会看到整个 App 都被@autoreleasepool包裹着,这就是 App 的顶层 pool。运行循环在其中运行,并在每个周期清空子 pool。
Q. 可以嵌套代码块吗?
可以。pool 会像栈一样逐层叠加。内部代码块关闭时,只会清空内部 pool,外部 pool 会保持不变。
简单总结,@autoreleasepool就是一个在指定时机清空“已预约释放的临时对象”的代码块。
如果你用 Instruments 确认循环中的内存像山一样飙升,可以试着用这个代码块包住循环内部。你会清楚看到图像变得平稳。
如果你对从 MRC(Manual Reference Counting,手动引用计数)转向 ARC 的历史感兴趣,也推荐阅读之前整理的《Objective-C 内存管理历史》。祝你今天编码愉快!

