??前面章節(jié)提到內(nèi)存釋放時(shí),經(jīng)常會(huì)說(shuō)到當(dāng)超出變量作用域時(shí),變量會(huì)被“自動(dòng)”釋放,其實(shí)這只是為了更加簡(jiǎn)單的說(shuō)明這個(gè)過(guò)程。實(shí)際上,在ARC模式下是系統(tǒng)幫你自動(dòng)插入了相應(yīng)的release邏輯。這個(gè)流程有沒(méi)有什么問(wèn)題呢?
??請(qǐng)看如下代碼:
-(NSObject *)objFactory() {
NSObject *obj = [[NSObject alloc];
return obj;
}
??這個(gè)函數(shù)生成了一個(gè)NSObject對(duì)象,并作為了返回值。那么在超出函數(shù)作用域前,要不要插入[obj release]呢?怎么樣都不合適,如果插入,該函數(shù)的caller若用weak指針持有函數(shù)返回值,馬上會(huì)將其置為nil;如果不插入,caller可能不知道要release這個(gè)對(duì)象,又會(huì)造成內(nèi)存泄露。
??為了解決這個(gè)問(wèn)題,引入了一個(gè)非常重要的技術(shù),就是Autorelease。簡(jiǎn)單來(lái)講,就是用autorelease來(lái)代替release,將要釋放的對(duì)象先放入一個(gè)“釋放池”,而不是馬上釋放。這個(gè)自動(dòng)釋放池,就是AutoreleasePool。
??AutoreleasePool是OC中的一種內(nèi)存自動(dòng)回收機(jī)制,它可以延遲加入AutoreleasePool中的變量release的時(shí)機(jī)。在正常情況下,創(chuàng)建的變量會(huì)在超出其作用域的時(shí)候release,但是如果將變量加入AutoreleasePool,那么release將延遲執(zhí)行。
??AutoreleasePool是如何實(shí)現(xiàn)的呢?一個(gè)線程對(duì)應(yīng)有一個(gè)Autoreleasepool,其結(jié)構(gòu)是一個(gè)指針棧。棧中存放的指針指向加入需要release的對(duì)象或者POOL_SENTINEL(哨兵對(duì)象,用于分隔Autoreleasepool)。棧中指向POOL_SENTINEL的指針就是Autoreleasepool的一個(gè)標(biāo)記。當(dāng)Autoreleasepool進(jìn)行出棧操作,每一個(gè)比這個(gè)哨兵對(duì)象后進(jìn)棧的對(duì)象都會(huì)release。這個(gè)棧是由一個(gè)以page為節(jié)點(diǎn)雙向鏈表組成,page根據(jù)需求進(jìn)行增減。Autoreleasepool對(duì)應(yīng)的線程存儲(chǔ)了指向最新page(也就是最新添加autorelease對(duì)象的page)的指針。
??AutoreleasePoolPage的結(jié)構(gòu)如下:
magic_t const magic; // 魔數(shù),校驗(yàn)用
id *next; // 棧頂指針
pthread_t const thread; // 當(dāng)前線程
AutoreleasePoolPage * const parent; // 父節(jié)點(diǎn)
AutoreleasePoolPage *child; // 子節(jié)點(diǎn)
uint32_t const depth; // 鏈表節(jié)點(diǎn)的個(gè)數(shù)
uint32_t hiwat; // high water mark(最高水位標(biāo)記)
??下面來(lái)看AutoreleasePool的幾個(gè)關(guān)鍵函數(shù)實(shí)現(xiàn):
static inline void *push()
{
id *dest;
if (DebugPoolAllocation) { // 區(qū)別調(diào)試模式
// Each autorelease pool starts on a new pool page.
// 調(diào)試模式下將新建一個(gè)鏈表節(jié)點(diǎn),并將一個(gè)哨兵對(duì)象添加到鏈表?xiàng)V? dest = autoreleaseNewPage(POOL_SENTINEL);
} else {
dest = autoreleaseFast(POOL_SENTINEL); // 添加一個(gè)哨兵對(duì)象到自動(dòng)釋放池的鏈表?xiàng)V? }
assert(*dest == POOL_SENTINEL);
return dest;
}
static inline id *autoreleaseFast(id obj)
{
AutoreleasePoolPage *page = hotPage(); // 獲取最新的page(即鏈表上最新的節(jié)點(diǎn))
if (page && !page->full()) {
return page->add(obj); // 在這個(gè)page存在且不滿的情況下,直接將需要autorelease的對(duì)象加入棧中
} else if (page) {
return autoreleaseFullPage(obj, page); // 在這個(gè)page已經(jīng)滿了的情況下,新建一個(gè)page并將obj對(duì)象放入新的page(即入棧)
} else {
return autoreleaseNoPage(obj); // 在沒(méi)有page的情況下,新建一個(gè)page并將obj對(duì)象放入新的page(即入棧)
}
}
id *add(id obj) // 入棧操作
{
assert(!full());
unprotect(); // 解除保護(hù)
id *ret = next; // faster than `return next-1` because of aliasing
*next++ = obj; // 將obj入棧到棧頂并重新定位棧頂
protect(); // 添加保護(hù)
return ret;
}
static inline void pop(void *token) // token指針指向棧頂?shù)牡刂? {
AutoreleasePoolPage *page;
id *stop;
page = pageForPointer(token); // 通過(guò)棧頂?shù)牡刂氛业綄?duì)應(yīng)的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(); // 記錄最高水位標(biāo)記
page->releaseUntil(stop); // 從棧頂開(kāi)始操作出棧,并向棧中的對(duì)象發(fā)送release消息,直到遇到第一個(gè)哨兵對(duì)象
// memory: delete empty children
// 刪除空掉的節(jié)點(diǎn)
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對(duì)象到自動(dòng)釋放池的鏈表?xiàng)V? assert(!dest || *dest == obj);
return obj;
}
??加入AutoreleasePool的對(duì)象,會(huì)在[pool drain]的時(shí)候release。那么drain方法到底在什么時(shí)候調(diào)用呢?這里又引出了一個(gè)Runloop的概念。對(duì)于每一個(gè)Runloop, 系統(tǒng)會(huì)隱式創(chuàng)建一個(gè)Autorelease pool,這樣所有的release pool會(huì)構(gòu)成一個(gè)象CallStack一樣的一個(gè)棧式結(jié)構(gòu),在每一個(gè)Runloop結(jié)束時(shí),當(dāng)前棧頂?shù)腁utorelease pool會(huì)被銷(xiāo)毀,這樣這個(gè)pool里的每個(gè)Object會(huì)被release。那什么是一個(gè)Runloop呢? 一個(gè)UI事件,Timer call, delegate call, 都會(huì)是一個(gè)新的Runloop。
??下一章,再詳細(xì)研究Runloop到底是什么。