iOS原理 理解AutoreleasePool释放对象的时机
在介绍AutoreleasePool基本概念的时候提到,AutoreleasePool
在销毁时,会统一给池中的所有对象发送一次release
消息。通过这个机制,我们可以立即或者延迟
释放对象。
延迟释放
Runloop
创建的AutoreleasePool
,只会在Runloop
即将休眠或退出的时候销毁,这时候池中的对象才会被释放,因此可以达到延迟释放的效果。这里以主线程Runloop
为例,看看主线程中的对象被延迟释放的情况。
- 断点查看主线程中的
AutoreleasePool
如图所示,直接在viewDidLoad
方法里下断点,运行后在控制台进行bt
输出,结果如下:
在输出结果里可以看到,此时主线程Runloop
已经创建了一个AutoreleasePool
。
- 在主线程中创建自动释放对象
这里创建两个UIImage
对象,其中一个是自动释放对象
,另一个为非自动释放对象
,以作对比。
//弱引用指针不会持有对象,不影响对象的引用计数
__weak UIImage *_img1 = nil;
__weak UIImage *_img2 = nil;
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
//创建两个UIImage对象
UIImage *img1 = [[UIImage alloc] init]; //非自动释放对象,不能被添加到AutoreleasePool
UIImage *img2 = [UIImage imageNamed:@"test.png"]; //自动释放对象,会被添加到AutoreleasePool
_img1 = img1;
_img2 = img2;
//打印两个对象的地址
NSLog(@" ==== %@:img1 = %p, img2 = %p", NSStringFromSelector(_cmd), _img1, _img2);
}
-(void)viewWillAppear:(BOOL)animated{
[super viewWillAppear:animated];
//打印两个对象的地址
NSLog(@" ==== %@:img1 = %p, img2 = %p", NSStringFromSelector(_cmd), _img1, _img2);
}
-(void)viewDidAppear:(BOOL)animated{
[super viewDidAppear:animated];
//打印两个对象的地址
NSLog(@" ==== %@:img1 = %p, img2 = %p", NSStringFromSelector(_cmd), _img1, _img2);
}
//打印结果
==== viewDidLoad:img1 = 0x600003274990, img2 = 0x6000032705a0
==== viewWillAppear::img1 = 0x0, img2 = 0x6000032705a0
==== viewDidAppear::img1 = 0x0, img2 = 0x0
从打印结果可知,_img1
在viewDidLoad
方法结束时就被释放了,而_img2
被延迟释放了。这是因为:
-
_img1
:是在viewDidLoad
方法里创建的临时对象,且为非自动释放对象
,所有权归viewDidLoad
方法持有,当方法结束时,就会被释放掉。 -
_img2
:虽然也是在viewDidLoad
方法里创建的临时对象,但它是自动释放对象
,会被添加到主线程Runloop
创建的AutoreleasePool
中,所有权归AutoreleasePool
持有,只有当Runloop
即将进入休眠或者退出时,才会销毁AutoreleasePool
,这时对象才能被释放,所以会延迟释放。
因此,程序运行后,主线程Runloop
会自动创建AutoreleasePool
,在主线程中创建的自动释放对象都会被添加到AutoreleasePool
中,这些对象都会被延迟释放。
立即释放
使用@autoreleasepool {}
代码块手动创建的AutoreleasePool
,当超出代码块的作用域时被销毁,这时会释放池中的对象,可以达到立即释放的效果。一般情况下,在编写循环时可能需要手动创建AutoreleasePool
。
- 循环内创建
AutoreleasePool
for (int i = 0; i<1000000; i++) {
@autoreleasepool {
UIImage *img = [UIImage imageNamed:@"test.png"];
NSLog(@" ==== %p", img);
}
}
运行后可以看到,在循环过程中内存并不会上涨。这是因为
每次迭代都会创建并销毁一个AutoreleasePool
,每次创建的UIImage
对象都会被添加到AutoreleasePool
,在销毁时就被释放了,所以内存不会上涨。
因此,在循环内使用自动释放池块可以在下一次迭代之前释放这些临时对象,这样就可以降低内存峰值。需要注意的是,如果循环内都是非自动释放对象
,就不需要手动创建AutoreleasePool
,即使创建了也起不了任何作用。
基于降低内存峰值的需求,苹果自己也在下面这几个方法里封装了
@autoreleasepool {}
,遍历时使用这几个方法性能更好。
enumerateObjectsUsingBlock
enumerateObjectsWithOptions
enumerateObjectsAtIndexes