第34条:以“自动释放池”降低内存峰值
1.在哪写自动释放池
自动释放池往往是不需要我们自己写的,每一个线程都有默认的自动释放池,每次执行一次runloop就会自动把自动释放池清空。
通常只有一个地方需要自己写自动释放池块,就是main函数。
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
这个自动释放池用来捕捉UIApplicationMain函数自动释放的对象,可以理解成最外围捕捉全部自动释放对象所用的池。
2.自动释放池的释放时机
⚠️:这个问题非常重要,我们也已经多次研究了,一定要记住。
在没有手加Autorelease Pool(@autoreleasepool{})的情况下,Autorelease对象是在当前的runloop迭代结束时释放的,而它能够释放的原因是系统在每个runloop迭代中都加入了自动释放池Push和Pop。
在每一个runloop的迭代过程中,在runloop里会注册两个observer,第一个observer监测即将进入runloop状态(kCFRunLoopEntry),并且调用_objc_autoreleasePoolPush()方法,创建一个自动释放池,这个方法的优先级最高,确保创建自动释放池的操作在其他所有操作之前执行。
第二个observer监测runloop的两个状态,一个是BeforeWaiting。在监测到这个状态以后调用_objc_autoreleasePoolPop()方法,销毁当前的自动释放池。并且调用_objc_autoreleasePoolPush()方法,创建新的自动释放池。另一个是exit,即将退出runloop这个状态。这个时候调用_objc_autoreleasePoolPush()方法,销毁自动释放池。
所以说,不是手写的自动释放池的生命周期是和他所在线程的runloop息息相关的,runloop迭代结束时才会销毁这个自动释放池,同时向其中的对象发送release消息。
3.利用自动释放池解决内存峰值问题
正是因为了解了自动释放池的释放时机,才产生了内存峰值问题。
比如说我们在一个for循环中创建了大量的局部变量:
for(int i = 0;i<10000;i++){
[self doSomethingWithInt: i];
}这个时候所有在for循环中的变量都被添加到自动释放池,但是这个时候runloop没有迭代结束,那么for循环中的这些变量始终没办法释放,内存就会持续上涨,如果这些对象占据很大的内存,那么很快程序就会发出内存警告了。
那么这个时候就可以在for循环里加一个自动释放池,把生成变量的代码包裹住,每次循环都会释放。
for(NSDictionary* record in databaseRecord){
@autoreleasepool{
Person* person = [[Person alloc] initWithRecord:record];
xxx;
}
}