
來,來,來看看這個平時不用,其實它一直那里的Autoreleasepool長什么樣~
其實在main.m 文件入口就已經(jīng)給我們加好了@autoreleasepool的內(nèi)容是這樣的:
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
所以整個 iOS 的應(yīng)用都是包含在一個自動釋放池 block 中的。
實際工作時其實是這樣的:
int main(int argc, const char * argv[]) {
{
void * atautoreleasepoolobj = objc_autoreleasePoolPush();
// do whatever you want
objc_autoreleasePoolPop(atautoreleasepoolobj);
}
return 0;
}
那objc_autoreleasePoolPush和objc_autoreleasePoolPop又是啥呢?
void *objc_autoreleasePoolPush(void) {
return AutoreleasePoolPage::push();
}
void objc_autoreleasePoolPop(void *ctxt) {
AutoreleasePoolPage::pop(ctxt);
}
就是AutoreleasePoolPage的兩個push 和pop方法,接下來我們這三個一個個看到底都是些啥玩意兒。
AutoreleasePoolPage
先看看 AutoreleasePoolPage 是個啥玩意兒呢?
其實,autoreleasepool 是沒有單獨的內(nèi)存結(jié)構(gòu)的,每一個自動釋放池都是由一系列的 AutoreleasePoolPage 組成的,并且每一個AutoreleasePoolPage 的大小都是 4096 字節(jié)(16 進制 0x1000)
AutoreleasePoolPage {
magic_t const magic;
id *next;
pthread_t const thread;
AutoreleasePoolPage * const parent;
AutoreleasePoolPage *child;
uint32_t const depth;
uint32_t hiwat;
}
-
magic用來校驗AutoreleasePoolPage結(jié)構(gòu)是否完整; -
next指向第一個可用的地址; -
thread指向當前的線程; -
parent指向父類 -
child指向子類

Push
static inline void *push()
{
//autoreleaseFast 關(guān)鍵
id *dest = autoreleaseFast(POOL_SENTINEL);
assert(*dest == POOL_SENTINEL);
return dest;
}
再看autoreleaseFast執(zhí)行具體的插入操作
static inline id *autoreleaseFast(id obj)
{
//hotPage 可以理解為當前正在使用的 AutoreleasePoolPage
AutoreleasePoolPage *page = hotPage();
if (page && !page->full()) {
//當前 page 存在且沒有滿時,直接將對象添加到當前 page 中
return page->add(obj);
} else if (page) {
//當前 page 存在且已滿時,創(chuàng)建一個新的 page ,并將對象添加到新創(chuàng)建的 page 中
return autoreleaseFullPage(obj, page);
} else {
//當前 page 不存在時,即還沒有 page 時,創(chuàng)建第一個 page ,并將對象添加到新創(chuàng)建的 page 中
return autoreleaseNoPage(obj);
}
}
page->add 添加對象
id *add(id obj) {
id *ret = next;
*next = obj;
next++;
return ret;
}
這個方法其實就是一個壓棧的操作,將對象加入 AutoreleasePoolPage 然后移動棧頂?shù)闹羔?/p>
autoreleaseFullPage(當前 hotPage 已滿)
static id *autoreleaseFullPage(id obj, AutoreleasePoolPage *page) {
do {
if (page->child) page = page->child;
else page = new AutoreleasePoolPage(page);
} while (page->full());
setHotPage(page);
return page->add(obj);
}
它會從傳入的 page 開始先遍歷整個雙向鏈表
- 一直遍歷,直到找到一個未滿的
AutoreleasePoolPage, - 如果找到最后還沒找到,就新建一個
AutoreleasePoolPage - 將該頁面標記成
hotPage - 調(diào)動
page->add方法添加對象。
autoreleaseNoPage(沒有 hotPage)
static id *autoreleaseNoPage(id obj) {
AutoreleasePoolPage *page = new AutoreleasePoolPage(nil);
setHotPage(page);
if (obj != POOL_SENTINEL) {
page->add(POOL_SENTINEL);
}
return page->add(obj);
}
POOL_SENTINEL是一個邊界對象 nil,用來區(qū)別每個page即每個 AutoreleasePoolPage邊界
如果當前內(nèi)存中不存在 AutoreleasePoolPage,就要構(gòu)建一個新的自動釋放池的雙向鏈表,將當前頁標記為 hotPage。但是第一個 AutoreleasePoolPage 是沒有parent 指針的,所以會先向這個page中添加一個POOL_SENTINEL 對象,來確保在pop調(diào)用的時候,不會出現(xiàn)異常。
push小結(jié)
一個 push 操作其實就是創(chuàng)建一個新的 autoreleasepool ,對應(yīng) AutoreleasePoolPage 的具體實現(xiàn)就是往 AutoreleasePoolPage 中的 next 位置插入一個 POOL_SENTINEL,并且返回插入的POOL_SENTINEL 的內(nèi)存地址。
Autorelease
- [NSObject autorelease]
└── id objc_object::rootAutorelease()
└── id objc_object::rootAutorelease2()
└── static id AutoreleasePoolPage::autorelease(id obj)
└── static id AutoreleasePoolPage::autoreleaseFast(id obj) //重點
├── id *add(id obj)
├── static id *autoreleaseFullPage(id obj, AutoreleasePoolPage *page)
│ ├── AutoreleasePoolPage(AutoreleasePoolPage *newParent)
│ └── id *add(id obj)
└── static id *autoreleaseNoPage(id obj)
├── AutoreleasePoolPage(AutoreleasePoolPage *newParent)
└── id *add(id obj)
autorelease跟 push 操作的實現(xiàn)非常相似。只不過 push 操作插入的是一個POOL_SENTINEL ,而 autorelease 操作插入的是一個具體的autoreleased對象。
Pop
先放一張圖:

pop在內(nèi)存中的變化就是長這個樣子。將邊界對象指向的這一頁 AutoreleasePoolPage 內(nèi)的對象釋放
回頭看一看 objc_autoreleasePoolPop
objc_autoreleasePoolPop(void *ctxt)
{
AutoreleasePoolPage::pop(ctxt);
}
pop 函數(shù)的入?yún)⒕褪?push 函數(shù)的返回值,也就是 POOL_SENTINEL 的內(nèi)存地址,即pool token 。當執(zhí)行pop 操作時,內(nèi)存地址在 pool token之后的所有autoreleased對象都會被 release。直到pool token所在 page 的 next 指向 pool token為止。
static inline void pop(void *token) {
//獲取當前 token 所在的 AutoreleasePoolPage
AutoreleasePoolPage *page = pageForPointer(token);
id *stop = (id *)token;
//釋放棧中的對象,直到 stop
page->releaseUntil(stop);
if (page->child) {
if (page->lessThanHalfFull()) {
page->child->kill();
} else if (page->child->child) {
page->child->child->kill();
}
}
}
-
pageForPointer獲取當前token所在的AutoreleasePoolPage
-- 通過內(nèi)存地址的操作,獲取當前指針所在頁的首地址 -
releaseUntil方法釋放棧中的對象,直到stop
-- 用循環(huán)持續(xù)釋放AutoreleasePoolPage中的內(nèi)容(objc_release),直到next指向了stop -
child的kill方法
--它會將當前頁面以及子頁面全部刪除
總結(jié)
AutoreleasePool = AutoreleasePoolPage (4096字節(jié)) * n;
AutoreleasePoolPage = push + autorelease+pop;
push 和autorelease最終都是調(diào)用 autoreleaseFast方法,變了花的往next位置插POOL_SENTINEL或?qū)ο?br>
pop 傳入邊界對象,然后對page 中的對象發(fā)送release 的消息
其實
通常情況下,我們是不需要手動添加 autoreleasepool 的,使用線程自動維護的 autoreleasepool 就好了。根據(jù)蘋果官方文檔中對 Using Autorelease Pool Blocks 的描述,我們知道在下面三種情況下是需要我們手動添加 autoreleasepool 的:
如果你編寫的程序不是基于 UI 框架的,比如說命令行工具;
如果你編寫的循環(huán)中創(chuàng)建了大量的臨時對象;
如果你創(chuàng)建了一個輔助線程。
參考資料
What is autoreleasepool? [duplicate]
NSAutoreleasePool
iOS之a(chǎn)utoreleasepool詳解
自動釋放池的前世今生 ---- 深入解析 Autoreleasepool
各個線程 Autorelease 對象的內(nèi)存管理