场景
官方推荐的使用 autoreleasepool
的场景:
非 UI 项目,比如命令行
使用 Xcode
创建一个默认的空命令行项目你会发现 main
默认添加了 @autoreleasepool
int main(int argc, const char * argv[]) {
@autoreleasepool {
}
return 0;
}
在 for 循环中需要大量用到临时变量
for
循环释放临时变量会等到循环全部结束,所以存在大量临时变量时需要使用 autoreleasepool
防止内存暴增。
for (...) {
@autoreleasepool {
......
}
}
使用枚举循环时不用添加 autoreleasepool
因为默认已经添加了
[array enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
// 这里被一个局部 @autoreleasepool 包围着
}];
使用 GCD 如果 block 中内存使用大时考虑用 autoreleasepool
虽然 dispatch queue
有自己的 autorelease pool
,但是 GCD
不能保证 pool
的释放时机,如果程序是对内存敏感度,那么要手动添加 autoreleasepool
。
dispatch_async(queue, ^{
@autoreleasepool {
......
}
});
NSAutoreleasePool 与 @autoreleasepool
- 在
ARC
中使用@autoreleasepool
- 在
MRC
中都能使用。
Autorelease 原理
@autoreleasepool {
....
}
会被翻译为
void *context = objc_autoreleasePoolPush();
...
objc_autoreleasePoolPop(context);
autorelease
使用 AutoreleasePoolPage
来实现管理内存,AutoreleasePoolPage
可以理解为一个栈,调用 objc_autoreleasePoolPush()
时会在栈内添加一个哨兵,@autoreleasepool
中创建的对象会被放入这个栈内,调用 objc_autoreleasePoolPop(context)
时,放在哨兵之上的栈内的对象会被调用 release
。
AutoreleasePoolPage
之间是一个双向链表,每个结点是一个 AutoreleasePoolPage
,一个 AutoreleasePoolPage
的大小是虚拟内存一页的大小。
释放时机
手动使用 @autoreleasepool
时的释放时机是 @autoreleasepool
结束时。
ARC
中非手动使用或 MRC
中调用 [objc autorelease]
时,autorelease
的时机是在当前 runloop
结束时。
一个 runloop
会在 Entry
时调用 objc_autoreleasePoolPush()
创建 pool
,在 Exit
时调用 objc_autoreleasePoolPop()
释放 pool
。
在 runloop
循环的过程中,在 BeforeWaiting
准备进入休眠时调用 objc_autoreleasePoolPop()
和 objc_autoreleasePoolPush()
释放旧的 pool
并创建新的 pool
。
参考
- 黑幕背后的Autorelease
- 深入理解RunLoop
- Using Autorelease Pool Blocks