AutoreleasePool 理解-应用-底层实现

867 阅读2分钟

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 互不影响,但同一变量只会释放一次。