前言
一般情况下,对象在超出作用域时会立即release
。比方说,在一个方法里创建一个局部对象:
-(void)test{
NSObject *obj = [[NSObject alloc] init];
}
当test
方法执行完,这个NSObject
对象就会被release
了。但有些时候,比如从工厂方法返回对象时,并不希望对象在超出作用域后立即release
,这就需要通过AutoreleasePool
来实现。
AutoreleasePool的基本介绍
AutoreleasePool(自动释放池)
是OC中的一种内存管理机制,它持有释放池里的对象的所有权,在自动释放池销毁时,统一给所有对象发送一次release
消息。通过这个机制,可以延迟对象的释放。
- 延迟释放
UIImage *img = [UIImage imageNamed:@"xxxx.png"];
这个UIImage
对象是在类方法imageNamed
里创建完后再返回,对象的所有权归方法持有,如果不延迟释放,在方法结束时对象就被释放了,返回的就为nil
。因此,需要将对象先加入AutoreleasePool
,所有权归自动释放池持有,只有自动释放池销毁时才释放,这样UIImage
对象才能在方法结束后正常返回。
AutoreleasePool的创建方式
通常使用@autoreleasepool {}
代码块来手动创建一个自动释放池
@autoreleasepool {
//这里创建自动释放的对象,创建的对象会被加入到AutoreleasePool对象里
... ...
}
这个代码块等价于
{
//创建一个AutoreleasePool对象
__AtAutoreleasePool *atautoreleasepoolobj = objc_autoreleasePoolPush();
//这里创建自动释放的对象,创建的对象会被加入到AutoreleasePool对象里
... ...
//给所有自动释放的对象发送一次release消息,并销毁AutoreleasePool对象
objc_autoreleasePoolPop(atautoreleasepoolobj)
}
`{}`表示AutoreleasePool对象的作用域
代码块的实现逻辑如下:
- 先通过调用
objc_autoreleasePoolPush
函数来创建一个AutoreleasePool
对象。 - 然后给在代码块里创建的每个自动释放的对象发送一个
autorelease
消息,将这些自动释放的对象加入到AutoreleasePool
对象里。 - 最后在
AutoreleasePool
对象将要销毁时,通过调用objc_autoreleasePoolPop
函数给池中每个自动释放的对象发送一次release
消息,再销毁AutoreleasePool
对象。
注意区分
AutoreleasePool对象
和自动释放的对象
,AutoreleasePool对象
指的是实例化的一个自动释放池(本质也是对象),而自动释放的对象
是指被加入到这个池中的对象。
AutoreleasePool
的原理可阅读后面的底层分析一文。
AutoreleasePool在Runloop中的创建和销毁
通常情况下,在平时开发中不需要手动创建自动释放池,因为Runloop
会自动创建和销毁AutoreleasePool
对象。
如上图所示,AutoreleasePool
在Runloop
中的创建和销毁的过程如下:
App启动后,系统在主线程
RunLoop
里注册了两个Observer
,其回调都是_wrapRunLoopWithAutoreleasePoolHandler()
。-
第一个
Observer
监视一个事件:-
Entry(即将进入Loop)
:调用objc_autoreleasePoolPush
来创建自动释放池。
-
-
第二个
Observer
监视了两个事件:-
Before waiting(准备进入休眠)
:先调用objc_autoreleasePoolPop
销毁旧的自动释放池,再调用objc_autoreleasePoolPush
创建一个新的自动释放池。 -
Exit(即将退出Loop)
:调用objc_autoreleasePoolPop
销毁自动释放池。
-
第一个
observe
的order
是-2147483647
,优先级最高,保证创建释放池发生在其他所有回调之前。
第二个Observer
的order
是2147483647
,优先级最低,保证销毁自动释放池发生在其他所有回调之后。
也就是说,在一个RunLoop
事件开始的时候会自动创建一个AutoreleasePool
,在事件结束时再自动销毁。上面举例的imageNamed
方法内部创建的对象也是加入到主线程RunLoop
创建的AutoreleasePool
中实现延迟释放的。因此,通常在开发中不需要开发者自己创建AutoreleasePool
。
手动创建AutoreleasePool的场景
虽然Runloop
会自动创建和销毁自动释放池,但在有些情况下还是需要手动创建AutoreleasePool
。苹果官方文档建议在下面这三种情况下可能需要开发者创建自动释放池:
-
编写不基于UI框架的程序,例如命令行工具。
这一点的原因不是特别清楚,猜测是不基于UI框架的程序,可能不响应用户事件,导致不自动创建和销毁自动释放池。
-
编写一个创建大量临时对象的循环。
在循环内使用自动释放池块可以在下一次迭代之前释放这些对象,有助于减少应用程序的最大内存占用,即
降低内存峰值
。 -
编写非Cocoa程序时创建子线程。
Cocoa程序中的每个线程都维护自己的自动释放池块堆栈。而编写一个非Cocoa程序,比如
Foundation-only program
,这时如果创建了子线程,若不手动创建自动释放池,自动释放的对象将会堆积得不到释放,导致内存泄漏。
这里就第二个场景举例,来说明在循环内使用AutoreleasePool
对于降低内存峰值的作用。
//情况一:循环内不使用AutoreleasePool
for (int i = 0; i<1000000; i++) {
NSString *string = [NSString stringWithFormat:@"%@", @"0123456789"];
NSLog(@" ==== %p", string);
}
//情况二:循环内使用AutoreleasePool
for (int i = 0; i<1000000; i++) {
@autoreleasepool {
NSString *string = [NSString stringWithFormat:@"%@", @"0123456789"];
NSLog(@" ==== %p", string);
}
}
分别运行上面两种情况可以看到,在循环过程中,第一种情况的内存占用一直在增加,第二种情况的内存不会增加。这是因为:
-
情况一
:循环过程中,创建的NSString
对象一直在堆积,只有在循环结束才一起释放,所以内存一直在增加。 -
情况二
:每一次迭代中都会创建并销毁一个AutoreleasePool
,而每一次创建的NSString
对象都会加入到AutoreleasePool
中,所以在每次AutoreleasePool
销毁时,NSString
对象就会被释放,这样内存就不会增加。
这个场景中AutoreleasePool
是通过立即释放对象来降低内存峰值,而前面又说自动释放池用来延迟对象的释放,这两者其实不矛盾,本质是一样的,都是在自动释放池销毁时调用objc_autoreleasePoolPop
来释放池中的对象。只不过调用的时机不同,这里的@autoreleasepool {}
是在超出自己的作用域时就调用函数来销毁,而前面的是在Runloop
休眠或退出时才调用函数来销毁,所以调用的时机不同,才会实现立即或者延迟
释放的目的。
@autoreleasepool {}
的作用域指的就是前面提到的{}
,是AutoreleasePool对象
的作用域。
哪些对象可以被添加到自动释放池?
在MRC
模式下,只要给对象发送autorelease
消息,这个对象就会被添加到自动释放池。但在ARC
模式下,是由编译器自动给对象发送autorelease
消息,且不会给所有的对象都发送,只会给被编译器识别为自动释放的对象
发送。一般来说,使用类方法(工厂方法)实例化的对象
才是自动释放的对象,才能被添加到自动释放池,而使用new、alloc、copy
关键字生成的对象和retain
了的对象,不会被添加到自动释放池中。
- 以
UIImage
对象为例
for (int i = 0; i<1000000; i++) {
@autoreleasepool {
//1.自动释放的对象,需要被添加到自动释放池中
UIImage *image = [UIImage imageNamed:@"test.png"];
//2.非自动释放的对象,不能被添加到自动释放池中
UIImage *image = [[UIImage alloc] init];
NSLog(@" ==== image = %p", image);
}
}
分别运行上面两种情况,第一种情况内存不会增加,第二种情况内存会增加。第二种情况虽然在@autoreleasepool {}
中创建对象,但由于不是自动释放的对象
,所以还是不能被添加到AutoReleasePool
中,只能在循环结束一起释放。因此,在ARC
模式下,只有自动释放的对象
才能被添加到AutoReleasePool
中,非自动释放的对象
在超出作用域时会被立即释放。
需要注意的是,
自动释放的对象
如果没有被添加到AutoReleasePool
中,就会产生内存泄露。
总结
总得来说,关于AutoreleasePool
的基本概念可以归纳以下几点:
-
AutoreleasePool(自动释放池)
持有释放池里的对象的所有权,在自动释放池销毁时,统一给所有对象发送一次release
消息。 - 在一个
RunLoop
事件开始的时候会自动创建一个AutoreleasePool
,在事件结束时再自动销毁,这样可以延迟对象的release
。 - 也可以使用
@autoreleasepool {}
来手动创建自动释放池,在循环中使用可以立即释放对象,降低内存峰值。 - 在
MRC
模式下,只要给对象发送autorelease
消息,这个对象就会被添加到自动释放池。 - 在
ARC
模式下,一般来说,使用类方法(工厂方法)实例化的对象才是自动释放的对象
,才能被添加到自动释放池。自动释放的对象
如果没有被添加到AutoReleasePool
中,就会产生内存泄露。 - 在
ARC
中,自动释放的对象
由编译器自主识别并发送autorelease
消息,添加到AutoReleasePool
中。