第三十四条 : 以“自动释放池降低内存峰值”

2017/10/9 posted in  第五章 内存管理

Objective-C的引用计数架构中,有一项特性叫做“自动释放池”(autoreleasepool)。释放对象有两种方式:一种是 调用release方法,使其保留计数立即递减;另一种是调用autorelease方法,将其加人“自动 释放池”中。自动释放池用于存放那些需要在稍后某个时刻释放的对象。清空(drain)自动释放池时,系统会向其中的对象发送release消息。

创建自动释放池的语句为:

@autoreleasepool{
    // ....
}

我们来看下面这个例子:

@autoreleasepool {
    NSString *string = [NSString stringWithFormat: @"1 = %i", 1]; 
    @autoreleasepool {
        NSNumber *number = [NSNumber numberWithlnt:1];
        }
}

上面这个例子展示了基本的用法,自动释放池于左花括号处创建, 并于对应的右花括号处自动清空。位于自动释放池范围内的对象,将在此范围末尾处收到release消息。自动释放池可以嵌套。系统在自动释放对象时,会把它放到最内层的池里。

autoreleasepool的常见用法为降低程序的内存峰值,比如下面这个代码:

NSArray *databaseRecords = /* ... */;
NSMutableArray *people = [NSMutableArray new];
for (NSDictionary *record in databaseRecords) {
    EOCPerson *person = [[EOCPerson alloc] initWithRecord:record];
    [people addObject:person];
}

上述代码,因为for语句会不断的创建person对象,造成应用程序内存不断上涨,在执行完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) 一样。系统创建好自动释放池之后,就将其推入栈中, 而清空自动释放池,则相当于将其从栈中弹出。在对象上执行自动释放操作,就等于将其放入栈顶的那个池里。

现在我们创建一个iOS程序之后,系统会默认有一个main.m文件其中有代码:

#import <UIKit/UIKit.h>
#import "AppDelegate.h"

int main(int argc, char * argv[]) {
    @autoreleasepool {
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}

我们用自动释放池来包裹应用程序的主入口点(main application entry point),除了上述主动添加了一个释放池,我们一般不需要主动添加,系统创建的线程一般默认都有自动释放池。

@autordeaSepool语法还有个好处:每个自动释放池均有其范围,可以避免无意间误用了那些在清空池后已为系统所回收的对象。例如:

@autoreleasepool {
    id object = [self createObject];
}
[self useObject:object];

上述代码无法编译,因为object变量出了自动释放池块的外围后就不可用了(已经被释放),所以在调用“useObject:”方法时不能用它做参数。

要点

  • 自动释放池排布在栈中,对象收到autorelease消息后,系统将其放入最顶端的池里。
  • 合理运用自动释放池,可降低应用程序的内存峰值。
  • @autoreleasepool这种新式写法能创建出更为轻便的自动释放池。