前面章节提到内存释放时,经常会说到当超出变量作用域时,变量会被“自动”释放,其实这只是为了更加简单的说明这个过程。实际上,在ARC模式下是系统帮你自动插入了相应的release逻辑。这个流程有没有什么问题呢?
请看如下代码:
-(NSObject *)objFactory() {
NSObject *obj = [[NSObject alloc];
return obj;
}
这个函数生成了一个NSObject对象,并作为了返回值。那么在超出函数作用域前,要不要插入[obj release]呢?怎么样都不合适,如果插入,该函数的caller若用weak指针持有函数返回值,马上会将其置为nil;如果不插入,caller可能不知道要release这个对象,又会造成内存泄露。
为了解决这个问题,引入了一个非常重要的技术,就是Autorelease。简单来讲,就是用autorelease来代替release,将要释放的对象先放入一个“释放池”,而不是马上释放。这个自动释放池,就是AutoreleasePool。
AutoreleasePool是OC中的一种内存自动回收机制,它可以延迟加入AutoreleasePool中的变量release的时机。在正常情况下,创建的变量会在超出其作用域的时候release,但是如果将变量加入AutoreleasePool,那么release将延迟执行。
AutoreleasePool是如何实现的呢?一个线程对应有一个Autoreleasepool,其结构是一个指针栈。栈中存放的指针指向加入需要release的对象或者POOL_SENTINEL
(哨兵对象,用于分隔Autoreleasepool)。栈中指向POOL_SENTINEL
的指针就是Autoreleasepool的一个标记。当Autoreleasepool进行出栈操作,每一个比这个哨兵对象后进栈的对象都会release。这个栈是由一个以page为节点双向链表组成,page根据需求进行增减。Autoreleasepool对应的线程存储了指向最新page(也就是最新添加autorelease对象的page)的指针。
AutoreleasePoolPage的结构如下:
magic_t const magic; // 魔数,校验用
id *next; // 栈顶指针
pthread_t const thread; // 当前线程
AutoreleasePoolPage * const parent; // 父节点
AutoreleasePoolPage *child; // 子节点
uint32_t const depth; // 链表节点的个数
uint32_t hiwat; // high water mark(最高水位标记)
下面来看AutoreleasePool的几个关键函数实现:
static inline void *push()
{
id *dest;
if (DebugPoolAllocation) { // 区别调试模式
// Each autorelease pool starts on a new pool page.
// 调试模式下将新建一个链表节点,并将一个哨兵对象添加到链表栈中
dest = autoreleaseNewPage(POOL_SENTINEL);
} else {
dest = autoreleaseFast(POOL_SENTINEL); // 添加一个哨兵对象到自动释放池的链表栈中
}
assert(*dest == POOL_SENTINEL);
return dest;
}
static inline id *autoreleaseFast(id obj)
{
AutoreleasePoolPage *page = hotPage(); // 获取最新的page(即链表上最新的节点)
if (page && !page->full()) {
return page->add(obj); // 在这个page存在且不满的情况下,直接将需要autorelease的对象加入栈中
} else if (page) {
return autoreleaseFullPage(obj, page); // 在这个page已经满了的情况下,新建一个page并将obj对象放入新的page(即入栈)
} else {
return autoreleaseNoPage(obj); // 在没有page的情况下,新建一个page并将obj对象放入新的page(即入栈)
}
}
id *add(id obj) // 入栈操作
{
assert(!full());
unprotect(); // 解除保护
id *ret = next; // faster than `return next-1` because of aliasing
*next++ = obj; // 将obj入栈到栈顶并重新定位栈顶
protect(); // 添加保护
return ret;
}
static inline void pop(void *token) // token指针指向栈顶的地址
{
AutoreleasePoolPage *page;
id *stop;
page = pageForPointer(token); // 通过栈顶的地址找到对应的page
stop = (id *)token;
if (DebugPoolAllocation && *stop != POOL_SENTINEL) {
// This check is not valid with DebugPoolAllocation off
// after an autorelease with a pool page but no pool in place.
_objc_fatal("invalid or prematurely-freed autorelease pool %p; ",
token);
}
if (PrintPoolHiwat) printHiwat(); // 记录最高水位标记
page->releaseUntil(stop); // 从栈顶开始操作出栈,并向栈中的对象发送release消息,直到遇到第一个哨兵对象
// memory: delete empty children
// 删除空掉的节点
if (DebugPoolAllocation && page->empty()) {
// special case: delete everything during page-per-pool debugging
AutoreleasePoolPage *parent = page->parent;
page->kill();
setHotPage(parent);
} else if (DebugMissingPools && page->empty() && !page->parent) {
// special case: delete everything for pop(top)
// when debugging missing autorelease pools
page->kill();
setHotPage(nil);
}
else if (page->child) {
// hysteresis: keep one empty child if page is more than half full
if (page->lessThanHalfFull()) {
page->child->kill();
}
else if (page->child->child) {
page->child->child->kill();
}
}
}
static inline id autorelease(id obj)
{
assert(obj);
assert(!obj->isTaggedPointer());
id *dest __unused = autoreleaseFast(obj); // 添加obj对象到自动释放池的链表栈中
assert(!dest || *dest == obj);
return obj;
}
加入AutoreleasePool的对象,会在[pool drain]的时候release。那么drain方法到底在什么时候调用呢?这里又引出了一个Runloop的概念。对于每一个Runloop, 系统会隐式创建一个Autorelease pool,这样所有的release pool会构成一个象CallStack一样的一个栈式结构,在每一个Runloop结束时,当前栈顶的Autorelease pool会被销毁,这样这个pool里的每个Object会被release。那什么是一个Runloop呢? 一个UI事件,Timer call, delegate call, 都会是一个新的Runloop。
下一章,再详细研究Runloop到底是什么。