关于 AutoreleasePool 的几点

场景

官方推荐的使用 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

你可能感兴趣的:(关于 AutoreleasePool 的几点)