Autoreleasepool自动释放池块提供了一个持有对象的所有权的机制,可以避免它立刻释放(如你从一个方法返回一个对象时).正常情况下,我们不需要创建自己的自动释放池块,但也有一些情况下,创建自动释放池是非常明智的(子线程开启新的任务,for循环生成大量对象的时候).
autorelease 与 runloop
autorelease 本质上就是延迟调用 release,实际上autorelease对象是在当前的runloop迭代结束时释放的,以下是在iOS 8.2模拟器中的测试代码:
_weak id reference = nil;
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
NSArray *arr = [NSArray arrayWithObjects:@"FlyElephant",@"Keso", nil];
reference = arr;
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
NSLog(@"数组:%@",reference);
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
NSLog(@"数组:%@",reference);
}
加入autorelease的测试代码:
@autoreleasepool {
NSArray *arr = [NSArray arrayWithObjects:@"FlyElephant",@"Keso", nil];
reference = arr;
}
autoreleasepool 与 runloop
autoreleasepool与runloop乍一看没有关系,如果对Runloop有研究,对下面这段文字应该有印象:
App启动后,苹果在主线程 RunLoop 里注册了两个 Observer,其回调都是 _wrapRunLoopWithAutoreleasePoolHandler()。
第一个 Observer 监视的事件是 Entry(即将进入Loop),其回调内会调用 _objc_autoreleasePoolPush() 创建自动释放池。其 order 是-2147483647,优先级最高,保证创建释放池发生在其他所有回调之前。
第二个 Observer 监视了两个事件: BeforeWaiting(准备进入休眠) 时调用_objc_autoreleasePoolPop() 和 _objc_autoreleasePoolPush() 释放旧的池并创建新池;Exit(即将退出Loop) 时调用 _objc_autoreleasePoolPop() 来释放自动释放池。这个 Observer 的 order 是 2147483647,优先级最低,保证其释放池子发生在其他所有回调之后。
在主线程执行的代码,通常是写在诸如事件回调、Timer回调内的。这些回调会被 RunLoop 创建好的 AutoreleasePool 环绕着,所以不会出现内存泄漏,开发者也不必显示创建 Pool 了。
autoreleasepool 原理
ARC下我们使用@autoreleasepool{}来使用一个AutoreleasePool,随后编译器将进行编译:
void *context = objc_autoreleasePoolPush();
// {}中的代码
objc_autoreleasePoolPop(context);
autoreleasepool最终底层是由autoreleasepoolpage实现,定义如下:
class AutoreleasePoolPage {
magic_t const magic;
id *next;
pthread_t const thread;
AutoreleasePoolPage * const parent;
AutoreleasePoolPage *child;
uint32_t const depth;
uint32_t hiwat;
};
1.autoreleasePool并没有单独的结构,而是由若干个AutoreleasePoolPage以双向链表的形式组合而成(分别对应结构中的parent指针和child指针)
2.AutoreleasePool是按线程一一对应的(结构中的thread指针指向当前线程)
3.AutoreleasePoolPage每个对象会开辟4096字节内存(也就是虚拟内存一页的大小
4.next指针作为游标指向栈顶最新add进来的autorelease对象的下一个位置
5.一个AutoreleasePoolPage的空间被占满时,会新建一个AutoreleasePoolPage对象,连接链表,后来的autorelease对象在新的page加入.
autoreleasepool 实战
正常开发中也没有见过哪个项目中到处都是autoreleasepool的,那么什么时候使用autoreleasepool呢?
1.如果你写的程序不是基于UI框架的,比如说命令行工具.(较少)
2.如果创建一个循环,创建了大量的临时对象,你可以使用自动释放池处理在下一次迭代前处理这些对象,避免占用大量内存.
iOS中有三种循环比遍历方式for、forin、enumerateObjectsUsingBlcok,实际上enumerateObjectsUsingBlcok内部已经通过@autoreleasepool{}操作进行了对象处理,for和forin的方式需要我们自己手动处理,因此enumerateObjectsUsingBlcok效率最高,内存占用最少.
3.创建次级线程,当你创建线程的时候,你需要创建释放器避免内存泄漏.
Each thread in a Cocoa application maintains its own stack of autorelease pool blocks. If you are writing a Foundation-only program or if you detach a thread, you need to create your own autorelease pool block.
If your application or thread is long-lived and potentially generates a lot of autoreleased objects, you should use autorelease pool blocks (like AppKit and UIKit do on the main thread); otherwise, autoreleased objects accumulate and your memory footprint grows. If your detached thread does not make Cocoa calls, you do not need to use an autorelease pool block.
在Cocoa应用程序中每个系统线程都有自己的自动释放池来维护,如果手动创建创建线程,需要手动创建自动释放池.
如果创建常驻线程可能会导致大量的autorelease对象,应该像AppKit和UIkit一样使用autoreleasepool,如果你不使用cocoa的创建线程(比如通过POSIX创建线程),那么不需要使用autoreleasepool.
参考资料
官方文档
黑幕背后的Autorelease
深入理解RunLoop
Objective-C Autorelease Pool 的实现原理