Autorelease Pool是Objective-C中的内存管理方式之一,它与线程和NSAutorelease类有关。每一个线程都拥有自己的Autorelease Pool栈,这个栈底层是由双向链表构成,而双向链表由类AutoreleasePoolPage表示。AutoreleasePoolPage是一个C++类,这个类的结构如下(只给出了部分相关成员变量):
class AutoreleasePoolPage { static pthread_key_t const key = AUTORELEASE_POOL_KEY; //和线程相关的key值 static size_t const SIZE = #if PROTECT_AUTORELEASEPOOL PAGE_MAX_SIZE; // must be multiple of vm page size #else PAGE_MAX_SIZE; // size and alignment, power of 2 #endif static size_t const COUNT = SIZE / sizeof(id); //每一个page的大小 id *next; //加入当前page中的Objective-C对象地址会传给next pthread_t const thread; //线程的引用 AutoreleasePoolPage * const parent; //指向父节点 AutoreleasePoolPage *child; //指向子节点 };
线程与Autorelease Pool栈的关系如下图所示:
上图中,加颜色部分表示加入到自动释放池中的对象,而next总是指向下一个可用位置。当前线程总是可以通过AutoreleasePoolPage中的key属性获取到位于栈最顶端(图中就是最右边)的AutoreleasePoolPage对象,这个对象被称为hot page。
需要注意的是,并不是一个AutoreleasePoolPage对象就代表一个自动释放池,一个自动释放池可以横跨多个AutoreleasePage对象。自动释放池之间是通过哨兵(Sentinel)来分隔的。那么什么是哨兵,NSAutoreleasePool对象和AutoreleasePoolPage对象之间又有什么关系呢?
当我们使用如下方法创建NSAutoreleasePool对象时:
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSAutoreleasePool对象的init方法实际上会调用AutoreleasePoolPage类的static push方法:
static inline void *push() { id *dest = autoreleaseFast(POOL_SENTINEL); assert(*dest == POOL_SENTINEL); return dest; }
AutoreleasePoolPage::push方法中的autoreleaseFast方法传入的就是一个哨兵,其实就是一个nil值。而AutoreleasePoolPage::autoreleaseFast方法会做3件事:
1 通过AutoreleasePoolPage的key属性获取当前线程对应的hot page,如果hot page存在并且未满,那么就向当前page中加入哨兵;
2 如果当前hot page已满,那么就创建一个新的AutoreleasePoolPage对象,加入到链表最顶端使成为hot page,并将哨兵加入到新的hot page中;
3 如果hot page根本不存在,那么也会创建一个新的AutoreleasePoolPage对象,使之成为hot page,并将哨兵加入到新hot page中。
当加入了一个哨兵之后,就代表一个新自动释放池开始。AutoreleasePoolPage::push方法会将这个哨兵地址返回回去,而NSAutoreleasePool对象会持有这个哨兵地址:
@interface NSAutoreleasePool : NSObject { @private void *_token; //保存哨兵地址 void *_reserved3; void *_reserved2; void *_reserved; }
NSAutoreleasePool对象当中的_token成员,就是用来保存哨兵地址的。因此,线程,AutoreleaesPoolPage对象,NSAutoreleasePool对象之间的关系可以表示成:
上图中有两个哨兵,因此有两个自动释放池,第一个自动释放池由NSAutoreleasePool对象_1表示,范围从第一个哨兵开始到第二个哨兵下面,第二个自动释放池由NSAutoreleasePool对象_2表示,范围从第二个哨兵开始直到结束。这也是自动释放池嵌套时的情形。
当自动释放池释放,即调用NSAutoreleasePool对象的release或者drain时会发生什么情况呢?
当调用NSAutoreleasePool对象的release或者drain方法时,它们将NSAutoreleasePool对象保存的哨兵地址传递会给AutoreleasePoolPoage的static pop方法,这个AutoreleasePoolPage::pop方法主要做了如下2件事:
1 通过相应算法,根据哨兵地址找到哨兵所在的AutoreleasePoolPage对象;
2 从hot page开始,直到哨兵对象为止,依次弹出加入到里面的Objective-C对象,并且向Objective-C对象发送release消息
假设释放了上面的NSAutoreleasePool对象_2,那么结果如下图所示:
从Apple关于NSAutoreleasePool的官方文档我们可以知道,NSAutoreleasePool对象无法被retain,因此,当AutoreleasePoolPage::pop方法调用完成之后,NSAutoreleasePool对象的release或者drain方法就会释放掉这个NSAutoreleasePool对象本身。到这里为止,我们又可以向上一个自动释放池当中添加新的Objective-C对象了。