自动释放池
先看一份关于autoreleasepool
的编译代码。
int main(int argc, const char * argv[]) {
@autoreleasepool {
}
return 0;
}
int main(int argc, const char * argv[]) {
{ __AtAutoreleasePool __autoreleasepool; }
return 0;
}
struct __AtAutoreleasePool {
__AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();}
~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);}
void * atautoreleasepoolobj;
};
在oc中autoreleasepool
在经过clang的编译之后,底层的实现是一个__AtAutoreleasePool
结构体,它拥有一个构造函数和一个析构函数。
很明显,在main()函数中,被自动释放池所包含的区域被当做为一块局部的代码块,进入代码块调用autoreleasepool
的构造函数,离开作用域时会调用析构函数释放局部变量。
AutoreleasePoolPageData
上面的构造函数和析构函数都是来自于AutoreleasePoolPage
这个结构体内,而AutoreleasePoolPage
又是继承自AutoreleasePoolPageData
那么看看它的内部源码:
/***********************************************************************
Autorelease pool implementation
A thread's autorelease pool is a stack of pointers.
Each pointer is either an object to release, or POOL_BOUNDARY which is
an autorelease pool boundary.
A pool token is a pointer to the POOL_BOUNDARY for that pool. When
the pool is popped, every object hotter than the sentinel is released.
The stack is divided into a doubly-linked list of pages. Pages are added
and deleted as necessary.
Thread-local storage points to the hot page, where newly autoreleased
objects are stored.
**********************************************************************/
struct AutoreleasePoolPageData
{
magic_t const magic;
__unsafe_unretained id *next;
pthread_t const thread;
AutoreleasePoolPage * const parent;
AutoreleasePoolPage *child;
uint32_t const depth;
uint32_t hiwat;
AutoreleasePoolPageData(__unsafe_unretained id* _next, pthread_t _thread, AutoreleasePoolPage* _parent, uint32_t _depth, uint32_t _hiwat)
: magic(), next(_next), thread(_thread),
parent(_parent), child(nil),
depth(_depth), hiwat(_hiwat)
{
}
};
AutoreleasePoolPage
究竟是什么?结合源码给出的注释它有以下几个特点:
- 线程的自动释放池是一个指针堆栈。
- 栈里面的指针要么是对象指针,要么就是
POOL_BOUNDARY
,这里理解为边界、哨兵。 - 释放池的
token
是指向POOL_BOUNDARY
。 - 释放池的栈是以
page
为单位的双向链表结构。 - 页的深度由
depth
标记。
总结一下就是,自动释放池是与线程相对应的双向链表,链表节点是以page
为单位。
AutoreleasePoolPage的初始化
AutoreleasePoolPage(AutoreleasePoolPage *newParent) :
AutoreleasePoolPageData(begin(),
objc_thread_self(),
newParent,
newParent ? 1+newParent->depth : 0,
newParent ? newParent->hiwat : 0){
}
id * begin() {
return (id *) ((uint8_t *)this+sizeof(*this));
}
自动释放池初始化的时候
-
_next
指向的是最新添加的 autoreleased 对象的下一个位置,初始化时指向
begin() 。 -
thread
指向当前线程。 -
parent
指向父节点,第一个节点为nil。 -
child
直接点,最后一个节点为nil。 -
depth
节点深度,默认是0. -
hiwat
代表最大入栈数量标记。
也就是说,autorelease
对象内部管理的变量是放在栈结构中,栈顶初始化位置是begin()
也就是结构体的末尾位置,也叫边界。每一次压栈,栈指针往高位移动8位。
每一个autorelease
对象管理的栈节点叫做page
,每一页的大小是4096个字节,autorelease
对象自身成员变量占用56个字节外,余下空间可以压入(4096-56)/8 = 505个对象指针,(除第一页有一个特殊标记只有504个外,objc779模拟器环境)。
最后一页会标记为hotPage()
,其他页标记为coldPage()
。
autorelease push和pop
对象是如何进入自动释放池的栈队列中?
AutoreleasePoolPage
压入一个对象指针基本流程:
- 判断
page
是否存在,是否满了。 -
page
不存在,新创建一个page
,并依据节点关系初始化。 - 如果
page
满了就会新建下一页。 - 如果
page
没满,就把*next
中存入压入的对象指针,并且next++
。
static inline id *autoreleaseFast(id obj)
{
AutoreleasePoolPage *page = hotPage();
if (page && !page->full()) {
return page->add(obj);
} else if (page) {
return autoreleaseFullPage(obj, page);
} else {
return autoreleaseNoPage(obj);
}
}
id *add(id obj)
{
ASSERT(!full());
unprotect();
id *ret = next; // faster than `return next-1` because of aliasing
*next++ = obj;
protect();
return ret;
}
pop的操作和push类似,原理类似,只不过反过来执行的时候,对每一个对象都执行了objc_release(obj);
函数释放对象,同时page
自身也会被处理掉。
不同的线程,autoreleasepool
不同,autoreleasepool
嵌套的时候,不会创建多个page
,但会有多个边界。