AutoreleasePool(自動(dòng)釋放池)是OC中的一種內(nèi)存自動(dòng)回收機(jī)制,它可以延遲加入AutoreleasePool中的變量release的時(shí)機(jī)。在正常情況下,創(chuàng)建的變量會(huì)在超出其作用域的時(shí)候release,但是如果將變量加入AutoreleasePool,那么release將延遲執(zhí)行。
AutoreleasePool創(chuàng)建和釋放
- App啟動(dòng)后,蘋果在主線程 RunLoop 里注冊了兩個(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 了。
也就是說AutoreleasePool創(chuàng)建是在一個(gè)RunLoop事件開始之前(push),AutoreleasePool釋放是在一個(gè)RunLoop事件即將結(jié)束之前(pop)。
AutoreleasePool里的Autorelease對象的加入是在RunLoop事件中,AutoreleasePool里的Autorelease對象的釋放是在AutoreleasePool釋放時(shí)。
AutoreleasePool實(shí)現(xiàn)原理
在終端中使用clang -rewrite-objc命令將下面的OC代碼重寫成C++的實(shí)現(xiàn)(參考:使用clang將OC代碼轉(zhuǎn)為C++
):
1)@autoreleasepool實(shí)質(zhì)上是一個(gè)__AtAutoreleasePool的結(jié)構(gòu)體對象;
下面是main 函數(shù)的C++實(shí)現(xiàn),聲明了一個(gè)__AtAutoreleasePool對象,然后調(diào)用應(yīng)用程序入口函數(shù);
int main(int argc, char * argv[]) {
/* @autoreleasepool */ {
__AtAutoreleasePool __autoreleasepool;
return UIApplicationMain(argc, argv, __null, NSStringFromClass(((Class (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("AppDelegate"), sel_registerName("class"))));
}
}
__AtAutoreleasePool的結(jié)構(gòu)如下所示:
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;
};
可以看出來,當(dāng)申明了一個(gè)對象__autoreleasepool,相當(dāng)于調(diào)用了objc_autoreleasePoolPush()函數(shù),該函數(shù)的作用即向堆棧內(nèi)壓入一個(gè)“自動(dòng)釋放池”;而當(dāng)int main()函數(shù)執(zhí)行完畢后,執(zhí)行__autoreleasepool的析構(gòu)函數(shù)objc_autoreleasePoolPop(atautoreleasepoolobj),用于釋放“自動(dòng)釋放池”;所以main()函數(shù)里可以大致這樣理解:
int main(int argc, char * argv[]) {
/* @autoreleasepool */ {
//創(chuàng)建自動(dòng)釋放池
__AtAutoreleasePool __autoreleasepool = objc_autoreleasePoolPush();
//TODO 執(zhí)行各種操作,將對象加入自動(dòng)釋放池
//釋放自動(dòng)釋放池
objc_autoreleasePoolPop(__autoreleasepool)
}
}
2) AutoreleasePool的本質(zhì)
已經(jīng)有人在蘋果的官方源碼里找到了關(guān)于AutoreleasePool的底層實(shí)現(xiàn)NSObject.mm里,源碼地址:https://opensource.apple.com/source/objc4/objc4-532/runtime/NSObject.mm.auto.html
objc_autoreleasePoolPush()和objc_autoreleasePoolPop(__autoreleasepool)的實(shí)現(xiàn)如下:
void* objc_autoreleasePoolPush(void)
{
if (UseGC) return NULL; //如果使用垃圾回收機(jī)制
return AutoreleasePoolPage::push();
}
void objc_autoreleasePoolPop(void *ctxt)
{
if (UseGC) return;
// fixme rdar://9167170
if (!ctxt) return;
AutoreleasePoolPage::pop(ctxt);
}
從上面可以發(fā)現(xiàn),C++類AutoreleasePoolPage才是實(shí)際的實(shí)現(xiàn)所在,找到AutoreleasePoolPage:
class AutoreleasePoolPage
{
#define POOL_SENTINEL 0
static size_t const SIZE =
#if PROTECT_AUTORELEASEPOOL
4096; // must be multiple of vm page size
#else
4096; // size and alignment, power of 2
#endif
magic_t const magic; //用于數(shù)據(jù)校驗(yàn)
id *next; //棧頂?shù)刂? pthread_t const thread; //所在的線程
AutoreleasePoolPage * const parent; //父對象
AutoreleasePoolPage *child; //子對象
uint32_t const depth; //page的序號(hào)?
uint32_t hiwat;
...
}
去除了一些不重要的代碼,可以看出這是一個(gè)典型的雙向列表結(jié)構(gòu),每個(gè)Page大小為4096 Byte,所以AutoreleasePool實(shí)質(zhì)上是一個(gè)雙向AutoreleasePoolPage列表;接下來分析一下自動(dòng)釋放池的工作過程:
創(chuàng)建自動(dòng)釋放池
void* objc_autoreleasePoolPush()內(nèi)部實(shí)際調(diào)用的是AutoreleasePoolPage::push()函數(shù),其實(shí)現(xiàn)如下:
static inline void *push()
{
if (!hotPage()) {
setHotPage(new AutoreleasePoolPage(NULL));
}
id *dest = autoreleaseFast(POOL_SENTINEL);
assert(*dest == POOL_SENTINEL);
return dest;
}
hotPage()是找出當(dāng)前的正在使用的page,第一次調(diào)用時(shí)hotPage為NULL,所以新建一個(gè)parent=NULL的AutoreleasePoolPage對象作為自動(dòng)釋放池加入棧中,并將其設(shè)置為hotPage,然后返回POOL_SENTINEL的地址賦值給main()函數(shù)里的變量 __autoreleasepool;
然后將一個(gè)哨兵對象POOL_SENTINEL壓入棧頂,即調(diào)用autoreleaseFast(POOL_SENTINEL)
static inline id *autoreleaseFast(id obj)
{
AutoreleasePoolPage *page = hotPage();
if (page && !page->full()) {
return page->add(obj);
} else {
return autoreleaseSlow(obj);
}
}
添加對象進(jìn)自動(dòng)釋放池
可以看出,如果當(dāng)前有page并且沒有滿,則直接將對象入棧頂(page->add(obj)):
id *add(id obj)
{
assert(!full());
unprotect();
*next++ = obj;
protect();
return next-1;
}
將對象壓入棧頂,然后將棧頂指針下移;
如果上述autoreleaseFast(id obj)中的page已經(jīng)滿了,則執(zhí)行autoreleaseSlow(obj)
id *autoreleaseSlow(id obj)
{
AutoreleasePoolPage *page;
page = hotPage();
//如果沒有page,則新建一個(gè)自動(dòng)釋放池,并添加obj對象進(jìn)釋放池
if (!page) {
objc_autoreleaseNoPool(obj);
return NULL;
}
//如果當(dāng)前hotPage已經(jīng)滿了,則以鏈表的形式新增一個(gè)page并添加到當(dāng)前page的后面,然后將此設(shè)置為hotPage;
do {
if (page->child) page = page->child;
else page = new AutoreleasePoolPage(page);
} while (page->full());
setHotPage(page);
return page->add(obj);
}
基本邏輯即,如果page不存在則,創(chuàng)建新的自動(dòng)釋放池(PoolPage),并將對象加進(jìn)池子;如果已經(jīng)存在自動(dòng)釋放池在棧中,且hotPage滿了,則遍歷其子page,如果存在沒滿(page->full()==NO)的子page,則將該子page設(shè)置為hotPage,否則如果都滿了,則以最后一個(gè)子page為父page,新建一個(gè)page,插入當(dāng)前的page鏈表,同樣設(shè)置該新建的page為hotPage,然后將自動(dòng)釋放對象加入page;
銷毀自動(dòng)釋放池
銷毀自動(dòng)釋放池的調(diào)用方式是:
void AutoreleasePoolPage::pop(void *token)
token 即push()的返回值,實(shí)際上就是POOL_SENTINEL的地址(__autoreleasepool)通過該地址即可找到所在Page的地址指針
static inline void pop(void *token)
{
AutoreleasePoolPage *page;
id *stop;
if (token) {
page = pageForPointer(token); //找到所在的page地址
stop = (id *)token; //POOL_SENTINEL的地址,從棧頂釋放對象直到這個(gè)位置
} else {
// Token 0 is top-level pool
page = coldPage();
stop = page->begin();
}
page->releaseUntil(stop); //對自動(dòng)釋放池中對象調(diào)用objc_release()進(jìn)行釋放
// memory: delete empty children
// hysteresis: keep one empty child if this page is more than half full
// special case: delete everything for pop(0)
if (!token) {
page->kill();
setHotPage(NULL);
} else if (page->child) {
if (page->lessThanHalfFull()) {
page->child->kill();
}
else if (page->child->child) {
page->child->child->kill();
}
}
}
該過程主要分為兩步:
一、page->releaseUntil(stop),對棧頂(page->next)到stop地址(POOL_SENTINEL)之間的所有對象調(diào)用objc_release(),進(jìn)行引用計(jì)數(shù)減1;
二、清空page對象page->kill(),有兩句注釋:
// hysteresis: keep one empty child if this page is more than half full
// special case: delete everything for pop(0)
除非是pop(0)方式調(diào)用,這樣會(huì)清理掉所有page對象;否則,在當(dāng)前page存放的對象大于一半時(shí),會(huì)保留一個(gè)空的子page,這樣估計(jì)是為了可能馬上需要新建page節(jié)省創(chuàng)建page的開銷吧.
對應(yīng)代碼
if (page->child) {
if (page->lessThanHalfFull()) {
page->child->kill(); //全部刪除
}
else if (page->child->child) {
page->child->child->kill();//保留一個(gè)子page
}
}
如果當(dāng)前的page中存放的對象少于一半,則子page全部刪除;如果當(dāng)前當(dāng)前的page存放的多余一半(意味著馬上將要滿),則保留一個(gè)子page,節(jié)省創(chuàng)建新page的開銷;