×

关于自动释放池@autoreleasepool你需要知道知识

96
周鶏
2016.12.06 20:37* 字数 1270

文章也同时在个人博客 http://kimihe.com/更新

引言

OC对象的生命周期取决于引用计数,我们有两种方式可以释放对象:一种是直接调用release释放;另一种是调用autorelease将对象加入自动释放池中。自动释放池用于存放那些需要在稍后某个时刻释放的对象。

本文将介绍自动释放池的原理和使用场景,并结合一道据说是优酷iOS的笔试题来举例说明自动释放池的妙用。

更多关于iOS内存管理的文章已经汇总至:深入总结iOS内存管理

自动释放池的创建

如果没有自动释放池而给对象发送autorelease消息,将会收到控制台报错。但一般我们无需担心自动释放池的创建问题。

我们的Mac以及iOS系统会自动创建一些线程,例如主线程和GCD中的线程,都默认拥有自动释放池。每次执行 “事件循环”(event loop)时,就会将其清空,这一点非常重要,请务必牢记! 关于事件循环,其涉及到runloop,可以看这篇文章:深入理解RunLoop

因此我们一般不需要手动创建自动释放池,通常只有一个地方需要它,那就是在main()函数里,如下:

int main(int argc, char * argv[]) {
    @autoreleasepool {
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}

这个main()函数里面的池并非必需。因为块的末尾是应用程序的终止处,即便没有这个自动释放池,也会由操作系统来释放。但是这些由UIApplicationMain函数所自动释放的对象就没有池可以容纳了,系统会发出警告。因此,这里的池可以理解成最外围捕捉全部自动释放对象所用的池。

@autoreleasepool的作用

大家可以先看一下下面的iOS笔试题的第5题(修改代码的错误),如下图:


优酷iOS笔试题

这段代码问题在哪里呢?题目的解答请继续阅读。笔者先给一个提示:与内存的释放有关。

现考虑如下代码:

for (int i = 0; i < 10000; i++) {
    [self doSthWith:object];
}

这段代码和笔试题关键部分大同小异。如果"doSthWith:"方法要创建一个临时对象,那么这个对象很可能会放在自动释放池里。笔试题中最后stringByAppendingString方法很有可能属于上述的方法。因此如果涉及到了自动释放池,那么问题也应该就出在上面。

注意:即便临时对象在调用完方法后就不再使用了,它们也依然处于存活状态,因为目前它们都在自动释放池里,等待系统稍后进行回收。但自动释放池却要等到该线程执行下一次事件循环时才会清空,这就意味着在执行for循环时,会有持续不断的新的临时对象被创建出来,并加入自动释放池。要等到结束for循环才会释放。在for循环中内存用量会持续上涨,而等到结束循环后,内存用量又会突然下降。

而如果把循环内的代码包裹在“自动释放池”中,那么在循环中自动释放的对象就会放在这个池,而不是在线程的主池里面。如下:

for (int i = 0; i < 1000000; i++) {
        @autoreleasepool {
            NSString *str = @"abc";
            str = [str lowercaseString];
            str = [str stringByAppendingString:@"xyz"];
        }        
}

新增的自动释放池可以减少内存用量,因为系统会在块的末尾把这些对象回收掉。而上述这些临时对象,正在回收之列。

自动释放池的机制就像“栈”。系统创建好池之后,将其压入栈中,而清空自动释放池相当于将池从栈中弹出。在对象上执行自动释放操作,就等于将其放入位于栈顶的那个池。

实验验证

我们可以通过实验进行验证。新建工程加入上述代码,并关闭ARC(不然是看不到区别的)。

在未添加autoreleasepool时,我们的堆内存实时分配情况如下图:

HeapWithoutAutoreleasepool

大家可以看到Persistent Bytes不断增加,到达100W次的创建峰值后(出for循环)开始逐步释放。因此图像是一个向上凸的曲线。

在加入autoreleasepool后,我们看到如下的曲线:

HeapWithAutoreleasepool

可以发现尽管字符串在不断地创建,但由于得到了及时的释放,堆内存始终保持在一个很低的水平。

其他注意点

@autoreleasepool语法还有一个好处,就是可以避免无意间误用那些在清空池之后已被系统回收的对象,例如:

@autoreleasepool {
    id obj = [self createObject];
}
[self useObject:obj];

上述代码在编译时就会基于错误警告,因为obj出了自动释放池就不可用了。

@autoreleasepool小结

  • 自动释放池排布在栈中,对象受到autorelease消息后,系统将其放入栈顶的池里。
  • 合理运用自动释放池,可以降低程序的内存峰值。
KimiTalk
Web note ad 1