前言
在MRC下, 我们需要手动管理内存, 写一大堆的retain, release代码, 稍不留神就会造成内存泄露; 而ARC下, 编译器帮我们屏蔽掉了这些繁琐的代码, 我们不需要再一条一条地写retain, release了, 可以专心地把精力放在业务逻辑, 技术上.
在MRC下, 调用[object autorelease]可以延迟对象的内存释放; 在ARC下, 我们甚至可以不需要知道 autorelease 是什么都能管理好内存. 编译器帮我们做了什么事情? 到底 autorelease 有什么神奇的地方? autorelease pool 又是个什么东西?下面我将会一一道来.
NSAutoreleasePool 与 @autoreleasepool
NSAutoreleasePool 是 Cocoa 用来支持引用计数内存管理机制的类, 当一个autorelease pool(自动释放池)被drain(销毁)的时候会对pool里的对象发送一条release的消息.
注意 : 在ARC下, 不能使用NSAutoreleasePool这个类来创建自动释放池, 而应该用@autoreleasepool { } 这个block, 官方文档源码如下 :
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
// Code benefitting from a local autorelease pool.
[pool release];
用以下代码代替上述代码 :
@autoreleasepool {
// Code benefitting from a local autorelease pool.
}
Ps : 官方文档说明, 使用@autoreleasepool这个block比NSAutoreleasePool更高效!并且在MRC环境下同样适用*
让我们用一张图片来了解对象调用autorelease的整个过程
补充几点 :
- 苹果官方文档说
An object can be put into the same pool several times, in which case it receives a release message for each time it was put into the pool.
意思是一个对象可以多次放入同一池子中, 并且每次放进去的时候都会调用release方法.. 但是我在MRC下做了测试, 结果是每调用一次autorelease方法, 池子中就多一个对象. 调用5次之后打印池子, 发现池子中有5个相同的对象.... 有知道的朋友麻烦告诉下谢谢 -
程序中至少存在一个自动释放池, 否则autoreleased对象将不能对应收到release消息而导致内存泄露.
- NSAutoreleasePool对象不能retain, 不能autorelease, 所以drain方法(或者release方法, 但是这两者有所不同, 下文会说)可以直接释放内存. 你应该在同一个上下文(调用创建这个池的同一个方法, 函数或者循环体)中drain一个自动释放池.
- MRC下需要对象调用autorelease才会入池, ARC下可以通过
__autoreleasing
修饰符, 否则的话看方法名, 非alloc/new/copy/mutableCopy开头的方法编译器都会自动帮我们调用autorelease方法. - 不一定要自己创建自动释放池, 但是有3种情况下是很必要的, 下面会讲.
- 自动释放池可以嵌套使用
下面讲autorelease pool 与 线程, RunLoop的关系...
autorelease pool 与 线程
每一个线程(包括主线程)都有一个NSAutoreleasePool栈. 当一个新的池子被创建的时候, push进栈. 当池子被释放内存时, pop出栈. 对象调用autorelease方法进入栈顶的池子中. 当线程结束的时候, 它会自动地销毁掉所有跟它有关联的池子.
如果你的应用或者线程是长期存在的并且有可能产生大量的autoreleased对象, 你应该定期地drain和create自动释放池, 否则, autorelease对象会在内存中堆积造成内存告急. 这里借用土土哥的一张图,
测试的内容:500000次循环,每次循环创建一个NSNumber实例和两个NSString实例。
图:红线表示没有用@autoreleasepool时的内存占用。
图:绿线表示用了@autoreleasepool优化后的内存占用!
如上图所示, 在每一次循环中, 先创建一个自动释放池, 然后循环结束的时候, 自动释放池销毁, release掉池中对象, 释放内存. 对比之下, 优劣不言而喻.
苹果也是这么做的, 数组的block遍历方法就是, 大家可以自行测试.
autorelease pool 与 RunLoop
程序运行 -> 开启事件循环 -> 发生触摸事件 -> 创建自动释放池 -> 处理触摸事件 -> 事件对象加入自动释放池 -> 一次事件循环结束, 销毁自动释放池.
苹果官方文档说 :
The Application Kit creates an autorelease pool on the main thread at the beginning of every cycle of the event loop, and drains it at the end, thereby releasing any autoreleased objects generated while processing an event
在开始每一个事件循环之前系统会在主线程创建一个自动释放池, 并且在事件循环结束的时候把前面创建的释放池释放, 回收内存. 这里不深入讲解RunLoop, 文章后面会给出几篇RunLoop的文章, 大家可以去看看.
如何管理自动释放池
这里介绍4个方法
- release
- drain
- autorelease
- retain
release 和 drain
这里把他们放在一块讲, 是因为他们在引用计数环境下都能销毁一个自动释放池, 为什么这里要特意说明引用计数环境, 因为在引用计数环境和垃圾回收(GC)环境下, 这两个方法不尽相同
在引用计数环境下, release 和 drain 效果相同, 均能销毁一个自动释放池.
在垃圾回收环境下, drain同上, release 则是一个空方法.
所以建议为了兼容性, 统一用drain吧.
autorelease 和 retain
抛出异常, 因为NSAutoreleasePool不能调用以上 autorelease 和 retain 方法.
怎么使用autorelease pool
由于@autoreleasepool同时兼容MRC和ARC编译环境(NSAutoreleasePool只能在MRC下使用), 所以以下均是以autorelease pool block来介绍使用.
Cocoa 希望代码总是在autorelease pool block中被执行, 否则autoreleased对象就得不到释放从而造成内存泄露.
什么时候需要自己手动创建autorelease pool
看苹果官方文档怎么说明 :
If you are writing a program that is not based on a UI framework, such as a command-line tool.
If you write a loop that creates many temporary objects.
You may use an autorelease pool block inside the loop to dispose of those objects before the next iteration. Using an autorelease pool block in the loop helps to reduce the maximum memory footprint of the application.If you spawn a secondary thread.
You must create your own autorelease pool block as soon as the thread begins executing; otherwise, your application will leak objects.你写的程序不是基于UI framework, 例如命令行项目
你写的循环创建了大量临时对象 -> 你需要在循环体内创建一个autorelease pool block并且在每次循环结束之前处理那些autoreleased对象. 在循环中使用autorelease pool block可以降低内存峰值
你创建了一个新线程
当线程开始执行的时候你必须立马创建一个autorelease pool block, 否则你的应用会造成内存泄露.
使用场景 :
- 利用@autoreleasepool优化循环, 如上述提过的例子所示
- 如果你的应用程序或者线程是要长期运行的并且有可能产生大量autoreleased对象, 你应该使用autorelease pool blocks
- 长期在后台中运行的任务, 方法
使用方法 : 不要太简单~~
@autoreleasepool {
// Code here
}
这里介绍一种特殊的情况
先上苹果官方源码
– (id)findMatchingObject:(id)anObject {
id match;
while (match == nil) {
@autoreleasepool {
// Do a search that creates a lot of temporary objects.
match = [self expensiveSearchForObject:anObject];
if (match != nil) {
[match retain]; /* Keep match around. */
}
}
}
return [match autorelease]; /* Let match go and return it. */
}
在block结束之后, 你要注意的是任何autoreleased对象已经被处理过了(release). 请不要对这个对象发送消息或者把这个对象当做方法的返回值返回. 会引发野指针错误.
解决方法 : 苹果是这么做的 : 在block内对match对象发送retain消息和在block外对match发送autorelease消息能延长match对象的生命周期并且允许match对象在block外部接收消息或者作为方法的返回值返回. 我们不需要再关心match什么时候释放, 因为它已经交给了上一层的autorelease pool去管理.
参考文档 :
NSAutoreleasePool Class Reference
Using Autorelease Pool Blocks
黑幕背后的Autorelease
@autoreleasepool-内存的分配与释放
这里推荐两个RunLoop的文章
深入理解RunLoop
RunLoop
欢迎大家关注@Jerry4me, 同时本文有错漏的点恳请大家不吝指出, 谢谢~