以“自动释放池块”降低内存峰值

《Effective Objective-C 2.0》读书笔记

Posted by Japho on August 10, 2018

释放对象有两种方式:一种是调用release方法,使其保留计数立即递减;另一种是调用autorelease方法,将其加入到“自动释放池”中。自动释放池用于存放那些需要稍后再某个时刻释放的对象。清空(drain)自动释放池时,系统会向其中的对象发送release信息。

一般情况下无需担心自动释放池的创建问题。系统会自动创建一些线程,例如主线程,或GCD机制中的线程,这些线程默认都是自动释放池的,每次执行事件循环(event loop)时,就会将其清空。因此,不需要自己创建“自动释放池块”。通常只有一个地方需要创建自动释放池,那就是在main函数中。用自动释放池来包裹应用程序的主入口点。

例如:

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

如果不写这个块的话,那么由UIApplicationMain函数所释放的那些对象,就没有自动释放池可以容纳了,系统会警告。这个池可以理解成最外围捕捉全部自动释放对象所用的池。

自动释放池于左花括号处创建,并于右花括号处自动清空。位于自动释放池范围内的对象,将在此范围末尾处收到release消息。自动释放池可以嵌套。系统在自动释放对象时,会把它放到最内层的池里。

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

@autoreleasepool{
NSNumber  *number =  [NSNumber  numberWithInt:1];
}
}

这两个对象都由类的工厂方法创建,这样创建出来的对象会自动释放。NSString对象在外围的自动释放池中,NSNumber在里层的自动释放池中。将自动释放池嵌套使用的好处是,可以借此控制应用程序的内存峰值,使其不致过高。

for (int i =  0;i < 10000;  i++)
{
[self doSomethingWithInt:i];
}

如果方法doSomethingWithInt:要创建临时对象,那么这些对象很有可能放在自动释放池里,但是即便这些对象在调用完方法之后就不再使用了,他们也依然处于存活状态,因为目前还在自动释放池里。等系统线程执行下一次事件循环时才会清空。这意味着在执行for循环时,会持续有新的对象创建出来,并加入自动释放池中。所有这种对象都要等for循环执行完毕才会释放,这样一来,在执行for循环时,应用程序所占内存量就会持续上涨,而等到所有临时对象都释放后,内存用量又会突然下降。

这里增加一个自动释放池即可解决此问题。把循环内的代码包裹在“自动释放池块”中,那么在循环中自动释放的对象就会放在这个池,而不是线程的主池里面。

for (int i =  0;i < 10000;  i++)
{
@autoreleasepool{
[self doSomethingWithInt:i];
}
}

加上这个自动释放池之后,执行循环时内存峰值就会降低。内存峰值:应用程序在某个特定的时间段内的最大内存用量。新增自动释放吃块可以减少峰值,是因为系统会在块的末位把某些对象回收掉。

自动释放池机制就像“栈”一样。系统创建好自动释放池之后,就将其推入栈中,而清空自动释放池,则相当于将其从栈中弹出。在对象上执行自动释放操作,就等于将其放入栈顶的那个池里。

要点:

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