在iOS內(nèi)存管理中,在ARC機(jī)制中,我們通常使用AutoreleasePool進(jìn)行內(nèi)存管理伦仍,本篇文章我們主要來(lái)分析autoreleasePool的原理。
分析
我們?cè)陂_(kāi)發(fā)中 使用 @autoreleasepool{}
方法將代碼運(yùn)行的對(duì)象痹籍,交給自動(dòng)釋放池管理呢铆,首先我們將以下代碼轉(zhuǎn)化為c++
函數(shù)
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSObject *obj = [[NSObject alloc] init];
NSLog(@"%@",obj);
}
return 0;
}
經(jīng)clang
編譯后,我們可以看到autoreleasepool
的結(jié)構(gòu)為如下結(jié)構(gòu)體
struct __AtAutoreleasePool {
__AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();}
~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);}
void * atautoreleasepoolobj;
};
我們可以看出蹲缠,在__AtAutoreleasePool
的構(gòu)造方法
中調(diào)用了objc_autoreleasePoolPush
方法棺克,在析構(gòu)方法
中調(diào)用了objc_autoreleasePoolPop
方法。
AutoreleasePoolPage
我們?cè)谠创a中找到了objc_autoreleasePoolPush
的實(shí)現(xiàn)
void *
objc_autoreleasePoolPush(void)
{
return AutoreleasePoolPage::push();
}
·AutoreleasePoolPage
是什么呢线定?
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)
{
}
};
它繼承自AutoreleasePoolPageData
娜谊,是雙向鏈表結(jié)構(gòu)
,其大小為 504 * 8 + 56
斤讥,一頁(yè)能存儲(chǔ) 504個(gè)8字節(jié) 的對(duì)象
-
magic
: 用來(lái)校驗(yàn)AutoreleasePoolPage
的完整性纱皆。 -
next
: 指向 最新添加到AutoreleasePoolPage
的對(duì)象湾趾,初始化時(shí)指向begin
-
thread
: 與page
綁定的線程。 -
parent
: 指向上一頁(yè)派草,第一頁(yè)
的parent
指向nil
搀缠。 -
child
: 指向下一頁(yè),最后一頁(yè)
的child
指向nil
近迁。 -
depth
: 代表頁(yè)的深度艺普,每增加一頁(yè),depth 加 1鉴竭。 -
hiwat
: 入棧對(duì)象的個(gè)數(shù)歧譬,即添加到該頁(yè)中對(duì)象的數(shù)量。
最后一頁(yè)為 hotPage
搏存,其余頁(yè)為coldPage
瑰步。
autoreleasePoolPush
在 push方法中
void *
objc_autoreleasePoolPush(void)
{
return AutoreleasePoolPage::push();
}
static inline void *push()
{
id *dest;
if (slowpath(DebugPoolAllocation)) {
// Each autorelease pool starts on a new pool page.
dest = autoreleaseNewPage(POOL_BOUNDARY);
} else {
dest = autoreleaseFast(POOL_BOUNDARY);
}
ASSERT(dest == EMPTY_POOL_PLACEHOLDER || *dest == POOL_BOUNDARY);
return dest;
}
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);
}
}
在 autoreleaseFast
方法中,實(shí)現(xiàn)了向自動(dòng)釋放池添加對(duì)象的操作璧眠。
那它是如何向 page
中添加對(duì)象的呢缩焦?
1: 首先會(huì)根據(jù)當(dāng)前thread
的key
,獲取hotPage
责静。
2 : 如果沒(méi)有hotPage
舌界,說(shuō)明還沒(méi)有page
。
2.1:創(chuàng)建一個(gè)新的 page
泰演。
2.2: 將當(dāng)前page
設(shè)置為 hotPage
呻拌。
2.3: 添加一個(gè)哨兵對(duì)象(邊界)。
2.4: 將對(duì)象添加到 hot頁(yè)中睦焕。
3: 如果有hotPage
藐握,會(huì)判斷hotPage
頁(yè)是否已滿
3.1: 如果滿了,會(huì)根據(jù)child
找到最后一頁(yè)垃喊,然后新建一個(gè) poolPage
猾普,將新創(chuàng)建的頁(yè)設(shè)置為 hotPage
,最后將對(duì)象添加到該頁(yè)中。
3.2: 如果沒(méi)滿本谜,直接將對(duì)象添加到hotPage
中初家。
哨兵對(duì)象(邊界)只有在調(diào)用objc_autoreleasePoolPush
方法中才會(huì)添加。
整體過(guò)程如下圖所示:
add
對(duì)象是怎樣添加到 hotPage
中的呢乌助?
id *add(id obj)
{
ASSERT(!full());
unprotect();
id *ret = next; // faster than `return next-1` because of aliasing
*next++ = obj;
protect();
return ret;
}
通過(guò)next
指針溜在,next
指向page頁(yè)
最后一個(gè)對(duì)象,將新對(duì)象添加到最后一個(gè)對(duì)象的后面他托。
objc_autoreleasePoolPop
在自動(dòng)釋放池進(jìn)行釋放的時(shí)候會(huì)調(diào)用 objc_autoreleasePoolPop(poolObj)
方法掖肋。
static inline void
pop(void *token)
{
AutoreleasePoolPage *page;
id *stop;
if (token == (void*)EMPTY_POOL_PLACEHOLDER) {
// Popping the top-level placeholder pool.
page = hotPage();
if (!page) {
// Pool was never used. Clear the placeholder.
return setHotPage(nil);
}
// Pool was used. Pop its contents normally.
// Pool pages remain allocated for re-use as usual.
page = coldPage();
token = page->begin();
} else {
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 (slowpath(PrintPoolHiwat || DebugPoolAllocation || DebugMissingPools)) {
return popPageDebug(token, page, stop);
}
return popPage<false>(token, page, stop);
}
1,如果poolObj
不為空,在判斷poolobj是不是begin且沒(méi)有父頁(yè)赏参,那么該autoreleasepool已沒(méi)有對(duì)象志笼。
2沿盅,如果 *poolobjc為哨兵對(duì)象(邊界),則執(zhí)行 popPage
函數(shù)
static void
popPage(void *token, AutoreleasePoolPage *page, id *stop)
{
if (allowDebug && PrintPoolHiwat) printHiwat();
page->releaseUntil(stop);
// memory: delete empty children
if (allowDebug && DebugPoolAllocation && page->empty()) {
// special case: delete everything during page-per-pool debugging
AutoreleasePoolPage *parent = page->parent;
page->kill();
setHotPage(parent);
} else if (allowDebug && 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();
}
}
}
void releaseUntil(id *stop)
{
// Not recursive: we don't want to blow out the stack
// if a thread accumulates a stupendous amount of garbage
while (this->next != stop) {
// Restart from hotPage() every time, in case -release
// autoreleased more objects
AutoreleasePoolPage *page = hotPage();
// fixme I think this `while` can be `if`, but I can't prove it
while (page->empty()) {
page = page->parent;
setHotPage(page);
}
page->unprotect();
id obj = *--page->next;
memset((void*)page->next, SCRIBBLE, sizeof(*page->next));
page->protect();
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
}
我們來(lái)著重分析releaseUntil
函數(shù)纫溃,在releaseUntil
方法中
- 1腰涧,先找到
hotPage
。 - 2紊浩,操作
next
指針南窗,對(duì) obj 執(zhí)行 objc_release操作,再偏移sizeof(obj)
大小找到下一個(gè)obj郎楼。 - 3,對(duì)當(dāng)前頁(yè)所有對(duì)象release完后窒悔,將其
parent
頁(yè)設(shè)置為hotpage呜袁,重復(fù)該過(guò)程。
其過(guò)程如下圖所示: