《编写高质量iOS与OS X代码的52个有效方法》--第五章 第34条
(ps:此乃读书笔记,加深记忆,仅供大家参考)
第34条 以“自动释放池块”降低内存峰值
Objective-C对象的生命期取决于其引用计数。释放对象有两种方式:一种是调用release方法,使其保留计数立即递减;另一种是调用autorelease方法,将其加入“自动释放池”中。自动释放池用于存放那些需要在稍后某个时刻释放的对象。
创建自动释放池所用语法如下:
@autoreleasepool {
//...
}
一般情况下无须担心自动释放池的创建问题。Mac OS X与iOS应用程序分别运行于Cocoa及Cocoa Touch环境中。系统会自动创建一些线程,比如说主线程或是“大中枢派发”(Grand Central Dispatch,GCD)机制中的线程,这些线程默认都有自动释放池,每次执行“事件循环”(event loop)时,就会将其清空。通常只有一个地方需要创建自动释放池,那就是main函数里,我们用自动释放池来包裹应用程序的主入口点(main application entry point)。
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
块的末尾恰好就是应用程序的终止处,而此时操作系统会把程序所占的全部内存都释放掉。但是如果不写这个块的话,那么由UIApplicationMain函数所自动释放的那些对象,就没有自动释放池可以容纳了,于是系统会发出警告信息来表明这一情况。所以说,这个池可以理解成最外围捕捉全部自动释放对象所用的池。
自动释放池于左花括号处创建,并于对应的右花括号处自动清空。位于自动释放池范围内的对象,将在此范围末尾处收到release消息。自动释放池可以嵌套。
@autoreleasepool {
NSString *string = [NSString stringWithFormat:@"1 = %i", 1];
@autoreleasepool {
NSNumber *number = [NSNumber numberWithInt:1];
}
}
将自动释放池嵌套的好处是,可以借此控制应用程序的内存峰值,使其不致过高。
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这种新式写法能创建出更为轻便的自动释放池。