最近在拜讀Draveness大佬的一篇文章自動(dòng)釋放池的前世今生 ---- 深入解析 autoreleasepool,看到文中給讀者留了一個(gè)問題:
我到現(xiàn)在也不是很清楚為什么要根據(jù)當(dāng)前頁(yè)的不同狀態(tài) kill 掉不同 child 的頁(yè)面岛啸。
關(guān)于AutoreleasePool
是什么秋冰,強(qiáng)力推薦閱讀原文讨惩,寫的很好睁壁。這里就不說(shuō)了,直接討論問題歼郭。
首先是整個(gè)pop
方法的實(shí)現(xiàn):
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 fully
if (page->lessThanHalfFull()) {
page->child->kill();
}
else if (page->child->child) {
page->child->child->kill();
}
}
}
我們先看看釋放的函數(shù)releaseUntil
遗契,它在釋放的時(shí)候其實(shí)會(huì)一直順著parent
往前釋放,直到參數(shù)stop
病曾,也就是說(shuō)可能一次性釋放好幾個(gè)page
牍蜂。
// 代碼有所刪減
void releaseUntil(id *stop)
{
while (this->next != stop) {
AutoreleasePoolPage *page = hotPage();
while (page->empty()) {
page = page->parent;
setHotPage(page);
}
id obj = *--page->next;
memset((void*)page->next, SCRIBBLE, sizeof(*page->next));
if (obj != POOL_BOUNDARY) {
objc_release(obj);
}
}
setHotPage(this);
}
然后我們來(lái)看看這段有疑問的代碼
// memory: delete empty children
if (DebugPoolAllocation && page->empty()) { // 分支1
// special case: delete everything during page-per-pool debugging
AutoreleasePoolPage *parent = page->parent;
page->kill();
setHotPage(parent);
} else if (DebugMissingPools && page->empty() && !page->parent) { // 分支2
// special case: delete everything for pop(top)
// when debugging missing autorelease pools
page->kill();
setHotPage(nil);
}
else if (page->child) { // 分支3
// hysteresis: keep one empty child if page is more than half fully
if (page->lessThanHalfFull()) {
page->child->kill();
}
else if (page->child->child) {
page->child->child->kill();
}
}
這塊代碼的作用是刪除空的子節(jié)點(diǎn),釋放內(nèi)存泰涂。pop之后三種情況:
- 當(dāng)前
page
為空鲫竞,直接kill掉當(dāng)前page
,然后把parent
設(shè)置為hotpage
逼蒙; - 當(dāng)前
page
為空从绘,而且沒有parent
,kill掉當(dāng)前page
是牢,hotpage
置為空僵井; - 當(dāng)前
page
不為空,但是有child
驳棱,如果當(dāng)前page
的空間占用不到一半批什,釋放child
,如果當(dāng)前page
的空間占用超過(guò)一半社搅,且child
還有child
驻债,直接釋放這個(gè)孫子輩的page
。(對(duì)于第三步注釋中的解釋是:keep one empty child if page is more than half fully)
我們?cè)倏纯?code>kill的實(shí)現(xiàn)罚渐,可以發(fā)現(xiàn)他是會(huì)順著child
一直往后釋放却汉,保證釋放節(jié)點(diǎn)的child page
都被釋放了。
void kill()
{
AutoreleasePoolPage *page = this;
while (page->child) page = page->child;
AutoreleasePoolPage *deathptr;
do {
deathptr = page;
page = page->parent;
if (page) {
page->unprotect();
page->child = nil;
page->protect();
}
delete deathptr;
} while (deathptr != this);
}
到這里就可以得出結(jié)論了:
- pop之后荷并,所有
child page
肯定都為空了合砂,且當(dāng)前page
一定是hotPage
- 系統(tǒng)為了節(jié)約內(nèi)存,判斷源织,如果當(dāng)前
page
空間使用少于一半翩伪,就釋放掉所有的child page
,如果當(dāng)前page
空間使用大于一半谈息,就從孫子page
開始釋放缘屹,預(yù)留一個(gè)child page
。
歡迎關(guān)注我的博客