52个有效方法(34) - 以“自动释放池块”降低内存峰值
-
Objective-C对象的生命周期取决于其引用计数。
-
在Objective-C的引用计数架构中,有一项特性叫做“自动释放池”(autorelease pool)。释放对象有两种方式:一种是调用release方法,使其保留计数立即递减;另一种是调用autorelease方法;将其加入“自动释放池”中。自动释放池用于存放那些需要在稍后某个时刻释放的对象。清空(drain)自动释放池时,系统会像其中的对象发送release消息。
-
不管对象是在自动释放池内还是外创建的,只要在自动释放池内调用autorelease方法;这个对象就会被放到自动释放池中。注意autorelease方法只有在自动释放池中使用才有效。
-
@autoreleasepool{}可以随意创建,也可以嵌套使用。一般情况下,无需担心自动释放池的创建问题。iOS应用程序运行在Cocoa Touch环境中。系统会自动创建一些线程,比如说主线程或GCD机制中的线程,这些线程默认都有自动释放池,每次执行“事件循环(event loop)”,就会将其清空。因此,不需要自己创建“自动释放池块”。通常只有一个地方需要创建自动释放池,那就是在main函数里。
//写在main函数里的这个自动释放池可以理解为最外围捕捉全部自动释放对象所用的池。
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
- 将自动释放池嵌套的好处是,可以借此控制应用程序的内存峰值,使其不致过高。
for (int i = 0; i < 1000; i++) {
[self doSomethingWithInt:i];
}
-
如果“doSomethingWithInt:”方法要创建临时对象,那么这些对象很可能会放在自动释放池里。然而释放池要等线程执行下一次事件循环时才会清空。这就意味着在执行for循环时,会持续有新对象创建出来,并加入自动释放池中。所有这种对象都要等for循环执行完才会释放。这样一来,在执行for循环时,应用程序所占内存量就会持续上涨,而等到所有临时对象都释放后,内存量又会突然降下来。
-
如果把循环内的代码包裹在“自动释放池块”中,那么在循环中自动释放的对象就会放在这个池,而不是线程的主池里面。
NSArray * databaseRecords = /* . . . */;
NSMutableArray *people = [NSMutableArray new];
for (NSDictionary *record in databaseRecords) {
@autoreleasepool {
EOCPerson * person = [[EOCPerson alloc] initWithRecord:record];
[people addObject:person];
}
}
-
加上这个自动释放池之后,应用程序在执行循环时的内存峰值就会降低,不再像原来那么高了。内存峰值(high-memory waterline)是指应用程序在某个特定时段内的最大内存用量(highest memory footprint)。
-
自动释放池机制就像“栈”(stack)一样。系统创建好自动释放池之后,就将其推入栈中,而清空自动释放池,则相当于将其从栈中弹出。在对象上执行自动释放操作,就等于将其放入栈顶的那个池里。
-
尽管自动释放池块的开销不太大,但毕竟还是有的,所以尽量不要建立额外的自动释放池。
-
@autoreleasepool语法还有个好处:每个自动释放池都有其范围,可以避免无意间误用了那些在清空池后已为系统所回收的对象。
要点
-
自动释放池排布在栈中,对象收到autorelease消息后,系统将其放入最顶端的池里。
-
合理运用自动释放池,可降低应用程序的内存峰值。
-
@autoreleasepool这种新式写法能创建出更为轻便的自动释放池。