AutoreleasePool
Q: Autorelease对象什么时候释放?
当问到这个问题的时候,很多答案都是“当前作用域大括号结束时释放”,显然木有正确理解Autorelease机制。
在没有手加Autorelease Pool的情况下,Autorelease对象是在当前的runloop迭代结束时释放的,而它能够释放的原因是系统在每个runloop迭代中都加入了自动释放池Push和Pop
APPKit 和 UIKit在Runloop 每次循环开启时,会隐式的创建一个autoreleasepool,并在每次循环结束时销毁, 在销毁时释放掉里面的所有autorelease对象. 在ARC中, 已弃用NSAutoreleasePool类创建自动是否池, 只能使用@autoreleasepool 来创建. 苹果官方提示 @autoreleasepool要比NSAutoreleasepool快6倍
autoreleasepool底层
// autoreleasepool 底层为一个结构体 对象
struct __AtAutoreleasePool{
// 构造函数 内部调用 objc_autoreleasepool push 这里基本可以看出 autoreleasepool应该是栈结构
__AtAutoreleasePool(){
atautoreleasepoolobj = objc_autoreleasePoolPush();
}
// 析构函数 调用objc_autoreleasePool pop 出栈
~__AtAutoreleasePool(){
objc_autoreleasePoolPop(autoreleasepoolobj);
}
void * autoreleasepoolobj;
}
@autoreleasepool底层是创建了一个__AtAutoreleasePool结构体对象;
在创建__AtAutoreleasePool结构体时会在构造函数中调用objc_autoreleasePoolPush()函数,并返回一个atautoreleasepoolobj(POOL_BOUNDARY存放的内存地址,下面会讲到);
在释放__AtAutoreleasePool结构体时会在析构函数中调用objc_autoreleasePoolPop()函数,并将atautoreleasepoolobj传入。
其中objc_autoreleasePoolPush()和objc_autoreleasePoolPop()都定义在AutoreleasePoolPage类中.
class AutoreleasePoolPage
{
# define EMPTY_POOL_PLACEHOLDER ((id*)1) // EMPTY_POOL_PLACEHOLDER:表示一个空自动释放池的占位符
# define POOL_BOUNDARY nil // POOL_BOUNDARY:哨兵对象
static pthread_key_t const key = AUTORELEASE_POOL_KEY;
static uint8_t const SCRIBBLE = 0xA3; // 用来标记已释放的对象
static size_t const SIZE = // 每个 Page 对象占用 4096 个字节内存
#if PROTECT_AUTORELEASEPOOL // PAGE_MAX_SIZE = 4096
PAGE_MAX_SIZE; // must be muliple of vm page size
#else
PAGE_MAX_SIZE; // size and alignment, power of 2
#endif
static size_t const COUNT = SIZE / sizeof(id); // Page 的个数
magic_t const magic; // 用来校验 Page 的结构是否完整
id *next; // 指向下一个可存放 autorelease 对象地址的位置,初始化指向 begin()
pthread_t const thread; // 指向当前线程
AutoreleasePoolPage * const parent; // 指向父结点,首结点的 parent 为 nil
AutoreleasePoolPage *child; // 指向子结点,尾结点的 child 为 nil
uint32_t const depth; // Page 的深度,从 0 开始递增
uint32_t hiwat;
......
}
从AutoreleasePoolPage结构可以发现:
* 自动释放池即所有的AutoreleasePoolPage对象是已栈为节点通过双向链表的结构组合而成的
* 自动释放池与线程(即Runloop)一一对应
* 每个 AutoreleasePoolPage对象 占用4096个字节空间, 其中56个字节用来存放自身成员变量,剩下的空间用来存放Autorelease对象的地址
POOL_BOUNDARY 哨兵
* POOL_BOUNDARY用来区分不同的释放池,以解决自动释放池嵌套问题;
* 每当创建一个自动释放池,就会调用push()方法,将一个POOL_BOUNDARY入栈,并返回其内存地址
* 当往自动释放池中添加 Autorelease对象时,将Autorelease对象地址入栈, 他们前面至少有一个POOL_BOUNDARY
* 当自动释放池销毁时, 会调用pop()方法并传入一个POOL_BOUNDARY,会从自动释放池中最后一个对象开始,依次给他们发送release消息,直到遇到POOL_BOUNDARY.
push 方法
static inline void *push()
{
id *dest;
if (DebugPoolAllocation) {
// Each autorelease pool starts on a new pool page.
dest = autoreleaseNewPage(POOL_BOUNDARY);
} else {
dest = autoreleaseFast(POOL_BOUNDARY); // 传入 POOL_BOUNDARY 哨兵对象
}
assert(dest == EMPTY_POOL_PLACEHOLDER || *dest == POOL_BOUNDARY);
return dest;
}
autoreleaseFast(id obj) 方法
static inline id *autoreleaseFast(id obj)
{
AutoreleasePoolPage *page = hotPage(); // 双向链表中的最后一个 Page
if (page && !page->full()) { // 如果当前 Page 存在且未满
return page->add(obj); // 将 autorelease 对象入栈,即添加到当前 Page 中;
} else if (page) { // 如果当前 Page 存在但已满
return autoreleaseFullPage(obj, page); // 创建一个新的 Page,并将 autorelease 对象添加进去
} else { // 如果当前 Page 不存在,即还没创建过 Page
return autoreleaseNoPage(obj); // 创建第一个 Page,并将 autorelease 对象添加进去
}
}
快速添加autorelease对象的三种情况
* 当当前page(hotPage, autoreleasePoolPage池的最后一个对象)存在并且未满时, 直接添加到当前page ,通过page->add(obj) 将obj对象入栈
* 当当前page存在但已满时, 通过autoreleaseFullPage(obj, page)将autorelease对象(obj)和当前page传入,创建一个新的page,并将obj对象添加进新的page
* 当前page不存在,即自动释放池里面还没有创建过page时, 通过autoreleaseNoPage(obj)方法创建第一个page,并将obj对象添加进去
page->add() 方法
// 其实就是将 autorelease对象添加到next指针所指向的地址,并将next指针指向这个对象的下一个位置的地址, 然后将该位置返回
id *add(id obj)
{
assert(!full());
unprotect();
id *ret = next; // faster than `return next-1` because of aliasing
*next++ = obj;
protect();
return ret;
}
autoreleaseFullPage(id obj, AutoreleasePoolPage *page) 方法
static __attribute__((noinline))
id *autoreleaseFullPage(id obj, AutoreleasePoolPage *page)
{
// The hot page is full.
// Step to the next non-full page, adding a new page if necessary.
// Then add the object to that page.
assert(page == hotPage());
assert(page->full() || DebugPoolAllocation);
do {
if (page->child) page = page->child;
else page = new AutoreleasePoolPage(page);
} while (page->full());
setHotPage(page);
return page->add(obj);
}
解析:
* 开启一个do while 循环, 先判断当前page的是否有子page(child是否存在), 如果存在将page指向child, 如果不存在就创建一个新的page, 并赋值给page 判断page是否已满, 未满则退出循环
* 将page设置成当前page hotPage()
* 将obj添加到page里面 并返回
autoreleaseNoPage 方法
static __attribute__((noinline))
id *autoreleaseNoPage(id obj)
{
// "No page" could mean no pool has been pushed
// or an empty placeholder pool has been pushed and has no contents yet
assert(!hotPage());
bool pushExtraBoundary = false;
if (haveEmptyPoolPlaceholder()) {
// We are pushing a second pool over the empty placeholder pool
// or pushing the first object into the empty placeholder pool.
// Before doing that, push a pool boundary on behalf of the pool
// that is currently represented by the empty placeholder.
pushExtraBoundary = true;
}
else if (obj != POOL_BOUNDARY && DebugMissingPools) {
// We are pushing an object with no pool in place,
// and no-pool debugging was requested by environment.
_objc_inform("MISSING POOLS: (%p) Object %p of class %s "
"autoreleased with no pool in place - "
"just leaking - break on "
"objc_autoreleaseNoPool() to debug",
pthread_self(), (void*)obj, object_getClassName(obj));
objc_autoreleaseNoPool(obj);
return nil;
}
else if (obj == POOL_BOUNDARY && !DebugPoolAllocation) {
// We are pushing a pool with no pool in place,
// and alloc-per-pool debugging was not requested.
// Install and return the empty pool placeholder.
return setEmptyPoolPlaceholder();
}
// We are pushing an object or a non-placeholder'd pool.
// Install the first page.
AutoreleasePoolPage *page = new AutoreleasePoolPage(nil);
setHotPage(page);
// Push a boundary on behalf of the previously-placeholder'd pool.
if (pushExtraBoundary) {
page->add(POOL_BOUNDARY);
}
// Push the requested object or pool.
return page->add(obj);
}
autoreleaseNoPage()方法中会创建第一个Page。该方法会判断是否有空的自动释放池存在,如果没有会通过setEmptyPoolPlaceholder()生成一个占位符,表示一个空的自动释放池。接着创建第一个Page,设置它为hotPage。最后将一个POOL_BOUNDARY添加进Page中,并返回POOL_BOUNDARY的下一个位置。
autorelease
autorelease 调用栈:
objc_autorelease -> objc_object::autorelease -> autorelease -> _objc_rootAutorelease -> objc_object::rootAutorelease2 -> AutoreleasePoolPage::autorelease
AutoreleasePoolPage::autorelease 方法
static inline id autorelease(id obj)
{
assert(obj);
assert(!obj->isTaggedPointer());
id *dest __unused = autoreleaseFast(obj);
assert(!dest || dest == EMPTY_POOL_PLACEHOLDER || *dest == obj);
return obj;
}
看出 autorelease对象是通过 autoreleaseFast()方法添加进page里面的 这样就印证了上面的说法
pop() 方法
static inline void pop(void *token)
{
AutoreleasePoolPage *page;
id *stop;
if (token == (void*)EMPTY_POOL_PLACEHOLDER) {
// Popping the top-level placeholder pool.
if (hotPage()) {
// Pool was used. Pop its contents normally.
// Pool pages remain allocated for re-use as usual.
pop(coldPage()->begin());
} else {
// Pool was never used. Clear the placeholder.
setHotPage(nil);
}
return;
}
page = pageForPointer(token);
stop = (id *)token;
if (*stop != POOL_BOUNDARY) {
if (stop == page->begin() && !page->parent) {
// Start of coldest page may correctly not be POOL_BOUNDARY:
// 1. top-level pool is popped, leaving the cold page in place
// 2. an object is autoreleased with no pool
} else {
// Error. For bincompat purposes this is not
// fatal in executables built with old SDKs.
return badPop(token);
}
}
if (PrintPoolHiwat) printHiwat();
page->releaseUntil(stop);
// 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();
}
}
}
pop方法传token即为POOLBOUND对于在page中的地址, 当销毁自动释放池时, 会调用pop( )方法将自动释放池中的autorelease对象按照出栈的顺序,从page的最后一个对象开始依次给他们发送一条release消息,直到遇到这个page的POOL_BOUNDARY.
* 判断token是不是 EMPTY_POOL_PLACEHOLDER,是的话就清空这个自动释放池
* token不是EMPTY_POOL_PLACEHOLDER的话, 就通过pageGetPointer(token)拿到token所在的page
* 通过page->releaseUntil(stop)方法将page中的autorelease对象全部释放, stop即为page的POOL_BOUNDARY的地址
* 判断当前page是否有child (page->child), 有的话销毁child
releaseUntil(id *stop) 方法
void releaseUntil(id *stop)
{
// 循环条件 当page的next指针指向stop的时候 退出循环
while (this->next != stop) {
// 从hotPage 当前page开始释放
AutoreleasePoolPage *page = hotPage();
// 当前page为空的时候 将page指向父page 将父page设置成当前page
while (page->empty()) {
page = page->parent;
setHotPage(page);
}
page->unprotect();
// next指针是指向最后一个对象的后一个位置,所以需要先减1
id obj = *--page->next;
memset((void*)page->next, SCRIBBLE, sizeof(*page->next));
page->protect();
// 如果obj对象不是POOL_BOUNDARY 释放obj
if (obj != POOL_BOUNDARY) {
objc_release(obj);
}
}
setHotPage(this);
#if DEBUG
// we expect any children to be completely empty
for (AutoreleasePoolPage *page = child; page; page = page->child) {
assert(page->empty());
}
#endif
}
releaseUntil方法其实就是通过一个while循环,从最后一个入栈的autorelease对象开始,依次给它们发送一条release消息,直到遇到这个POOL_BOUNDARY。