Autorelease 制是 iOS 提供延遲釋放對(duì)象的一種機(jī)制,放棄對(duì)象所有權(quán),但又不想對(duì)象會(huì)給立即釋放.這個(gè)時(shí)候 Autorelease 就能為你解決這個(gè)問(wèn)題,他可以當(dāng)做寄存處,你可以把對(duì)象寄存在它那兒,然后在合適的時(shí)間去釋放寄存對(duì)象。下面讓我們深入
Autorelease。
從添加對(duì)象開(kāi)始
在 MRC 時(shí)代,可以使用 [xxx autorelease],來(lái)添加一個(gè)對(duì)象到一個(gè) Autorelease pool 中,什么是 Autorelease pool ? Autorelease pool 是對(duì)象池,統(tǒng)一管理 autorelease 對(duì)象的地方。
在 ARC 時(shí)代蘋(píng)果禁用了 autorelease 方法,不能用了,不怕,我們還有 @autorelease{}可以使用。
為什么我們要添加 autorelease 對(duì)象,我們可以舉一個(gè)很常見(jiàn)的例子:你需要在一個(gè)函數(shù)中處理大量零碎的對(duì)象,然而函數(shù)結(jié)束之后才會(huì)釋放這些對(duì)象,這個(gè)就可以包裹一個(gè) @autorelease{} 可以有效的控制內(nèi)存的占用。
AutoreleasePool 結(jié)構(gòu)
在我們寫(xiě)下 @autorelease{}的時(shí)候,編譯器為我們轉(zhuǎn)換成
void *context = objc_autoreleasePoolPush();
// 你的代碼
objc_autoreleasePoolPop(context);
這2個(gè)函數(shù)在做什么呢?實(shí)際都是對(duì)AutoreleasePoolPage的封裝
void *objc_autoreleasePoolPush(void) {
return AutoreleasePoolPage::push();
}
void objc_autoreleasePoolPop(void *ctxt) {
AutoreleasePoolPage::pop(ctxt);
}
AutoreleasePoolPage 對(duì)象結(jié)構(gòu)如下圖

我們可以注意到child和parent2個(gè)字段,可以看出AutoreleasePool是由多個(gè) AutoreleasePoolPage組成的雙向列表

AutoreleasePool Push
每個(gè)AutoreleasePoolPage 的大小都是 4096 字節(jié),除去 7 字節(jié)存儲(chǔ)成員變量,其他部分都是用來(lái)存放 Autorelease 對(duì)象的指針。在 AutoreleasePool Push的時(shí)候系統(tǒng)會(huì)初始化一個(gè)AutoreleasePoolPage,填充相應(yīng)的字段,首先填入一個(gè)哨兵對(duì)象。
哨兵對(duì)象定義為:
#define POOL_SENTINEL nil
為什么要最先添加哨兵對(duì)象呢?哨兵對(duì)象也是 nil ,在push 的時(shí)候添加到棧頂是為了在 Pop (release)的時(shí)候,會(huì)連續(xù) Pop (release)到第一個(gè)哨兵對(duì)象。

添加完哨兵對(duì)象之后就將 next 指針指向下一個(gè)添加的 Autorelease 對(duì)象。以此加入對(duì)象,如果一個(gè)AutoreleasePoolPage被用慢,那么會(huì)開(kāi)啟一個(gè)新的AutoreleasePoolPage,并且將前一個(gè)的child指針指向新的AutoreleasePoolPage,添加雙向鏈表節(jié)點(diǎn)。
AutoreleasePool Pop
Pop 操作會(huì)以此釋放棧頂對(duì)象,會(huì)一直釋放對(duì)象到一個(gè)哨兵對(duì)象,也就是上文說(shuō)的。
當(dāng)然 Pop 也支持 Pop 到指定對(duì)象。
Autoreleasepool 與 Runloop 的關(guān)系
Autoreleasepool 處理了 RunLoop 的3個(gè)事件
- Entry(即將進(jìn)入Loop),這個(gè)時(shí)候會(huì)調(diào)用 _objc_autoreleasePoolPop 來(lái)創(chuàng)建一個(gè)新的釋放池,
- BeforeWaiting(準(zhǔn)備進(jìn)入休眠) 時(shí)調(diào)用_objc_autoreleasePoolPop() 和 _objc_autoreleasePoolPush() 釋放舊的池并創(chuàng)建新池。
- Exit(即將退出Loop) 時(shí)調(diào)用 _objc_autoreleasePoolPop() 來(lái)釋放自動(dòng)釋放池
看到一些有趣的問(wèn)題。
什么對(duì)象自動(dòng)加入到 autoreleasepool中?
- 非 alloc/new/copy/mutableCopy 的方法生成的對(duì)象如
[NSArray array]。 - __weak修飾符 修飾的對(duì)象。
- id的指針或?qū)ο蟮闹羔樤跊](méi)有顯式指定時(shí)會(huì)被附加上__autorealeasing修飾符
子線程默認(rèn)不會(huì)開(kāi)啟 Runloop,那出現(xiàn) Autorelease 對(duì)象如何處理?不手動(dòng)處理會(huì)內(nèi)存泄漏嗎?
子線程如果沒(méi)有創(chuàng)建 Pool ,但是產(chǎn)生了 Autorelease 對(duì)象,就會(huì)調(diào)用 autoreleaseNoPage 方法。在這個(gè)方法中,會(huì)自動(dòng)幫你創(chuàng)建一個(gè) hotpage,也就是默認(rèn)生成一個(gè) AutoreleasePoolPage來(lái)添加 autorelease 對(duì)象。