Objective-C Autorelease Pool 的實(shí)現(xiàn)原理
自動(dòng)釋放池的前世今生 ---- 深入解析 Autoreleasepool
AutoreleasePool是通過一個(gè)以AutoreleasePoolPage為結(jié)點(diǎn)的雙向鏈表來實(shí)現(xiàn)的。AutoreleasePoolPage滿后會(huì)自動(dòng)創(chuàng)建下一個(gè)AutoreleasePoolPage。
Autorelease Pool Blocks
使用clang -rewrite-objc命令將下面的Objective-C代碼重寫成C++代碼:
@autoreleasepool {
}
只保留了部分代碼:
extern "C"?__declspec(dllimport)?void?*?objc_autoreleasePoolPush(void);
extern "C"?__declspec(dllimport)?void objc_autoreleasePoolPop(void?*);
struct __AtAutoreleasePool {
? __AtAutoreleasePool()?{atautoreleasepoolobj?=?objc_autoreleasePoolPush();}
?~__AtAutoreleasePool()?{objc_autoreleasePoolPop(atautoreleasepoolobj);}
void?*?atautoreleasepoolobj;
};
/* @autoreleasepool */?
{
?? ?__AtAutoreleasePool __autoreleasepool;
}
蘋果對(duì)@autoreleasepool{}的實(shí)現(xiàn)真的是非常巧妙,真正可以稱得上藝術(shù)的代碼。蘋果通過一個(gè)__AtAutoreleasePool類型的局部變量 __autoreleasepool來實(shí)現(xiàn)@autoreleasepool{}。當(dāng)聲明__autoreleasepool時(shí),構(gòu)造函數(shù)被執(zhí)行,即執(zhí)行atautoreleasepoolobj?=?objc_autoreleasePoolPush();。當(dāng)出了當(dāng)前作用域時(shí)(即大括號(hào)),析構(gòu)函數(shù)~__AtAutoreleasePool()被執(zhí)行,即調(diào)用用objc_autoreleasePoolPop(atautoreleasepoolobj);。(微信團(tuán)隊(duì)對(duì)特殊字符的處理也用到了相同的技巧)。也就是說@autoreleasepool{}的代碼實(shí)現(xiàn)可以簡(jiǎn)化為如下:
/* @autoreleasepool */?{
void?*atautoreleasepoolobj?=?objc_autoreleasePoolPush();
?//?用戶代碼,所有接收到?autorelease?消息的對(duì)象會(huì)被添加到這個(gè)?autoreleasepool?中
??? objc_autoreleasePoolPop(atautoreleasepoolobj);
}
objc_autoreleasePoolPush和objc_autoreleasePoolPop的實(shí)現(xiàn):
void *objc_autoreleasePoolPush(void) {
?? ?return AutoreleasePoolPage::push();
}
void objc_autoreleasePoolPop(void*ctxt) {?
?? ?AutoreleasePoolPage::pop(ctxt);
}
由此可見內(nèi)部其實(shí)調(diào)用的是AutoreleasePoolPage中相關(guān)的方法
那這里的 AutoreleasePoolPage 是什么東西呢?
其實(shí),autoreleasepool 是沒有單獨(dú)的內(nèi)存結(jié)構(gòu)的,它是通過以 AutoreleasePoolPage 為結(jié)點(diǎn)的雙向鏈表來實(shí)現(xiàn)的。
一個(gè)空的AutoreleasePool的內(nèi)存結(jié)構(gòu)如下:

1. magic 用來校驗(yàn) AutoreleasePoolPage 的結(jié)構(gòu)是否完整;
2. next 指向最新添加的 autoreleased 對(duì)象的下一個(gè)位置,初始化時(shí)指向 begin() ;
3. thread 指向當(dāng)前線程;
4. parent 指向父結(jié)點(diǎn),第一個(gè)結(jié)點(diǎn)的 parent 值為 nil ;
5. child 指向子結(jié)點(diǎn),最后一個(gè)結(jié)點(diǎn)的 child 值為 nil ;
6. depth 代表深度,從 0 開始,往后遞增 1;
7. hiwat 代表 high water mark 。
當(dāng)next==begin()時(shí),表示AutoreleasePage為空,當(dāng)next==end()時(shí)表示AutoreleasePage已滿。每一個(gè)AutoreleasePage的大小都是4096字節(jié)
。我們打開 runtime 的源碼工程,在?NSObject.mm?文件的第 438-932 行可以找到 autoreleasepool 的實(shí)現(xiàn)源碼。
通過閱讀源碼,我們可以知道:
每一個(gè)線程的 autoreleasepool 其實(shí)就是一個(gè)指針的堆棧;
每一個(gè)指針代表一個(gè)需要 release 的對(duì)象或者 POOL_SENTINEL(哨兵對(duì)象,代表一個(gè) autoreleasepool 的邊界);
一個(gè) pool token 就是這個(gè) pool 所對(duì)應(yīng)的 POOL_SENTINEL 的內(nèi)存地址。當(dāng)這個(gè) pool 被 pop 的時(shí)候,所有內(nèi)存地址在 pool token 之后的對(duì)象都會(huì)被 release ;
這個(gè)堆棧被劃分成了一個(gè)以 page 為結(jié)點(diǎn)的雙向鏈表。pages 會(huì)在必要的時(shí)候動(dòng)態(tài)地增加或刪除;
Thread-local storage(線程局部存儲(chǔ))指向 hot page ,即最新添加的 autoreleased 對(duì)象所在的那個(gè) page 。
雙向鏈表:

push操作
?? ?其實(shí)是創(chuàng)建一個(gè)新的AutoreleasePool,對(duì)應(yīng)AutoreleasePoolPage就是在AutoreleasePoolPage的next位置插入一個(gè)POOL_SENTINEL指針(哨兵指針)。next移動(dòng)至下一個(gè)地址。POOL_SENTINEL作為push函數(shù)的返回值。
第一次調(diào)用push操作就會(huì)創(chuàng)建一個(gè)新的autorelease pool。即往AutoreleasePoolPage中插入一個(gè)POOL_SENTINEL,并且返回插入的POOL_SENTINEL的內(nèi)存地址。
autorelease操作
其實(shí)就是將自己的地址插入AutoreleasePoolPage。
[obj autorelease],NSObject中-autorelease方法的實(shí)現(xiàn):
-?(id)autorelease?{
return?((id)self)->rootAutorelease();
}
通過查看((id)self)->rootAutorelease()的方法調(diào)用,發(fā)現(xiàn)調(diào)用的其實(shí)是AutoreleasePoolPage的autorelease函數(shù)
__attribute__((noinline,used))
id
objc_object::rootAutorelease2()
{
??? assert(!isTaggedPointer());
??? return AutoreleasePoolPage::autorelease((id)this);
}
?AutoreleasePoolPage的autorelease函數(shù),它跟push操作非常相似。只不過push操作插入的是一個(gè)POOL_SENTINEL,而autorelease操作插入的是一個(gè)具體的autoreleased對(duì)象(指針)。
static?inline?id?autorelease(id obj)
{
??? assert(obj);
??? assert(!obj->isTaggedPointer());
id?*dest __unused?=?autoreleaseFast(obj);
??? assert(!dest?||?*dest?==?obj);
??? return obj;
}
Pop操作
同理,前面提到的objc_autoreleasePoolPop(void *)?函數(shù)本質(zhì)上也是調(diào)用的AutoreleasePoolPage的pop函數(shù)。
void?objc_autoreleasePoolPop(void?*ctxt)
{
if?(UseGC)?return;
?// fixme?rdar://9167170
if?(!ctxt)?return;
??? AutoreleasePoolPage::pop(ctxt);
}
pop函數(shù)的參數(shù)就是push的返回值,也就是POOL_SENTINEL。當(dāng)執(zhí)行pop操作時(shí),內(nèi)存地址POOL_SENTINEL之后的對(duì)象都會(huì)被release。直到POOL_SENTINEL所在page的next指向POOL_SENTINEL為止。
下圖是某個(gè)線程的AutoreleasePool堆棧的內(nèi)存結(jié)構(gòu)圖,在這個(gè)堆棧中有兩個(gè)POOL_SENTINEL,也就是表示有兩個(gè)autoreleasepool。該堆棧由三個(gè)AutoreleasePoolPage節(jié)點(diǎn)組成,第一個(gè)AutoreleasePoolPage為ColdPage(),最后一個(gè)為HotPage()。其中前兩個(gè)AutoreleasePoolPage已經(jīng)滿了,最后一個(gè)結(jié)點(diǎn)保存了最新添加的autoreleased對(duì)象objr3的內(nèi)存地址。

如果執(zhí)行pop(token1)操作,那么該堆棧的內(nèi)存結(jié)構(gòu)將會(huì)變成如下圖所示:

第二個(gè)POOL_SENTINEL指針之后的對(duì)象調(diào)用release方法,內(nèi)存得到釋放。
什么情況下需要手動(dòng)添加AutoreleasePool:
Each thread in a Cocoa application maintains its own stack of autorelease pool blocks.?
If you are writing a Foundation-only program or if you detach a thread, you need to create your own autorelease pool block.
If your application or thread is long-lived and potentially generates a lot of autoreleased objects, you should use autorelease pool blocks (like AppKit and UIKit do on the main thread); otherwise, autoreleased objects accumulate and your memory footprint grows. If your detached thread does not make Cocoa calls, you do not need to use an autorelease pool block.
1.有大量的零時(shí)對(duì)象,例如編寫的循環(huán)中創(chuàng)建了大量的零時(shí)對(duì)象
2.編寫的的程序不是基于UI框架的,比如說命令行工具
3.創(chuàng)建一個(gè)輔助線程
AutoreleasePool的創(chuàng)建和銷毀
即將進(jìn)入Runloop的時(shí)候創(chuàng)建AutoreleasePool。
RunLoop即將進(jìn)入休眠的時(shí)候釋放并創(chuàng)建新的Runloop。
即將退出Runloop的時(shí)候釋放自動(dòng)釋放池。
App啟動(dòng)后,蘋果在主線程?RunLoop?里注冊(cè)了兩個(gè)?Observer,其回調(diào)都是?_wrapRunLoopWithAutoreleasePoolHandler()。
第一個(gè)?Observer?監(jiān)視的事件是?Entry(即將進(jìn)入Loop),其回調(diào)內(nèi)會(huì)調(diào)用?_objc_autoreleasePoolPush()創(chuàng)建自動(dòng)釋放池。其?order?是-2147483647,優(yōu)先級(jí)最高,保證創(chuàng)建釋放池發(fā)生在其他所有回調(diào)之前。
第二個(gè)?Observer?監(jiān)視了兩個(gè)事件:?BeforeWaiting(準(zhǔn)備進(jìn)入休眠)時(shí)調(diào)用_objc_autoreleasePoolPop()和?_objc_autoreleasePoolPush()釋放舊的池并創(chuàng)建新池;Exit(即將退出Loop)時(shí)調(diào)用_objc_autoreleasePoolPop()來釋放自動(dòng)釋放池。這個(gè)?Observer?的?order?是?2147483647,優(yōu)先級(jí)最低,保證其釋放池子發(fā)生在其他所有回調(diào)之后。
在主線程執(zhí)行的代碼,通常是寫在諸如事件回調(diào)、Timer回調(diào)內(nèi)的。這些回調(diào)會(huì)被?RunLoop?創(chuàng)建好的?AutoreleasePool?環(huán)繞著,所以不會(huì)出現(xiàn)內(nèi)存泄漏,開發(fā)者也不必顯示創(chuàng)建?Pool?了。