1. AutoreleasePool 简介
1.1 ARC 与 Autorelease
ARC 在编译期会在合适的地方插入对象的 release/autorelease 语句。对于需要 Autorelease 的对象,会扔进 AutoreleasePool,延迟释放时机,直到当前 RunLoop 结束时 AutoreleasePool 被销毁才将其中的对象释放。
1.2 Autorelease 的对象
- alloc、copy、mutableCopy、new 的对象:release,作用域结束时就释放
- [NSMutableArray array]、[NSObject object] 的对象:autorelease,AutoreleasePool 销毁时才被释放
1.3 线程-RunLoop-AutoreleasePool
线程的 observer 观察到 RunLoop 即将开始和休眠/结束,会调用 autoreleasePool 的 push 和 pop;
每个线程都有一个 RunLoop,以懒加载的形式创建;
每个线程都会维护自己的 AutoreleasePool 堆栈;
2. AutoreleasePool 应用
2.1 在 for 循环中创建大量临时对象时用 AutoreleasePool 以降低内存占用峰值
for (int i = 0; i < 100000000; i ++) {
@autoreleasepool {
NSString * str = [NSString stringWithFormat:@"AutoReleasePool"];
NSString *tempstr = str;
}
}
block 枚举器内部相对于 for 循环做了优化,包含 autoreleasePool:
[array enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
// 这里被一个局部@autoreleasepool包围着
}];
3. AutoreleasePool 底层实现
AutoreleasePool 是由若干个 AutoreleasePoolPage 以双向链表的形式组合而成。
3.1 AutoreleasePoolPage
AutoreleasepoolPage 通过压栈的方式来存储每个autorelease的对象(从低地址到高地址),每个 page 占用虚拟内存一页的大小。
class AutoreleasePoolPage {
# define POOL_BOUNDARY nil //哨兵对象
static size_t const COUNT = SIZE / sizeof(id); // 对象数量
id *next; //指向栈顶最新添加的autoreleased对象的下一个位置,初始化时指向 `begin()`
pthread_t const thread; //指向当前线程;
AutoreleasePoolPage * const parent; //指向父结点
AutoreleasePoolPage *child; //指向子结点
uint32_t const depth; //深度
...
}
3.2 Push
将哨兵对象 POOL_BOUNDARY
插入,调用 autoreleaseFast(obj)。
autoreleaseFast(obj):
当前 page 存在且未满时,压栈进当前 page;
当前无 page 或 当前 page 已满,创建新的 page 并压栈进 page,并连接双向链表指针。
使用@autoreleasepool { } 本质就是调用 push() 方法;
3.3 Autorelease
将需要 autorelease 的对象插入,和 push 一样调用 autoreleaseFast(obj);
3.4 Pop
外部循环遍历 autoreleased 对象,对每个对象 release,直到哨兵对象 POOL_BOUNDARY
标识结束,并配置新的 hotPage;
嵌套 AutoreleasePool 互不影响,但同一变量只会释放一次。