一、Autorelease Pool是什么
AutoreleasePool(自動(dòng)釋放池)是OC中的一種內(nèi)存自動(dòng)回收機(jī)制,它可以延遲加入AutoreleasePool中的變量release的時(shí)機(jī)。在正常情況下,創(chuàng)建的變量會(huì)在超出其作用域的時(shí)候release,但是如果將變量加入AutoreleasePool,那么release將延遲執(zhí)行。
二、Autorelease對(duì)象到底什么時(shí)候被釋放
很多人說(shuō)在“當(dāng)前作用域大括號(hào)結(jié)束時(shí)釋放”,顯然沒(méi)有正確理解Autorelease機(jī)制。
在沒(méi)有手加 Autorelease Pool的情況下,Autorelease 對(duì)象是在當(dāng)前的 runloop迭代結(jié)束 時(shí)釋放的,而它能夠釋放的原因是系統(tǒng)在每個(gè) runloop 迭代中都加入了 自動(dòng)釋放池Push和Pop
三、在
MRC中,調(diào)用[obj autorelease]來(lái)延遲內(nèi)存的釋放是一件簡(jiǎn)單自然的事,ARC下,我們甚至可以完全不知道Autorelease就能管理好內(nèi)存。而在這背后,objc和編譯器都幫我們做了哪些事呢?
如下Demo:
#import <Foundation/Foundation.h>
// 生成兩個(gè)全局weak變量用來(lái)觀(guān)察實(shí)驗(yàn)對(duì)象
__weak NSString *weak_String;
__weak NSString *weak_StringAutorelease;
- (void)viewDidLoad {
[super viewDidLoad];
@autoreleasepool {
[self createStr];
NSLog(@"------in the autoreleasepool------");
NSLog(@"%@", weak_String);
NSLog(@"%@\n\n", weak_StringAutorelease);
}
NSLog(@"------in the main()------");
NSLog(@"%@", weak_String);
NSLog(@"%@\n\n", weak_StringAutorelease);
}
-(void)createStr
{
// 創(chuàng)建常規(guī)對(duì)象
NSString *string = [[NSString alloc] initWithFormat:@"Hello, World!"];
// 創(chuàng)建autorelease對(duì)象
NSString *stringAutorelease = [NSString stringWithFormat:@"Hello, World! Autorelease"];
weak_String = string;
weak_StringAutorelease = stringAutorelease;
NSLog(@"------in the createString()------");
NSLog(@"%@", weak_String);
NSLog(@"%@\n\n", weak_StringAutorelease);
}
運(yùn)行結(jié)果如下:

- 首先在
createString函數(shù)中創(chuàng)建了一個(gè)常規(guī)NSString對(duì)象和一個(gè)autorelease對(duì)象,然后分別賦值給兩個(gè)weak全局變量用于觀(guān)察目標(biāo)對(duì)象。通過(guò)打印結(jié)果可以看到,在createString函數(shù)中兩個(gè)對(duì)象都是正常存在的,出了createString函數(shù)在autoreleasepool中,常規(guī)對(duì)象已經(jīng)被釋放,而autorelease對(duì)象依然存在。在autoreleasepool外,autorelease對(duì)象也被釋放了。
四、AutoreleasePool 的底層實(shí)現(xiàn)
- 蘋(píng)果通過(guò)聲明一個(gè)
__AtAutoreleasePool類(lèi)型的局部變量__autoreleasepool實(shí)現(xiàn)了@autoreleasepool{}。 - 看看
__AtAutoreleasePool的定義:
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;
};
我們可以分析出,單個(gè)自動(dòng)釋放池的執(zhí)行過(guò)程就是:
objc_autoreleasePoolPush() —> [object autorelease] —> objc_autoreleasePoolPop(void *)
五、AutoreleasePoolPage
在objc_autoreleasePoolPush()函數(shù)的實(shí)現(xiàn):
void *
objc_autoreleasePoolPush(void)
{
if (UseGC) return nil;
return AutoreleasePoolPage::push();
}
同樣objc_autoreleasePoolPop()函數(shù)的實(shí)現(xiàn):
void
objc_autoreleasePoolPop(void *ctxt)
{
if (UseGC) return;
AutoreleasePoolPage::pop(ctxt);
}
我們發(fā)現(xiàn)這兩個(gè)函數(shù)的實(shí)現(xiàn)都是調(diào)用了AutoreleasePoolPage類(lèi)中的方法。于是我們可以斷定,AutoreleasePool是通過(guò)AutoreleasePoolPage類(lèi)來(lái)實(shí)現(xiàn)的。
看看AutoreleasePoolPage定義的屬性:
magic_t const magic;
id *next;
pthread_t const thread;
AutoreleasePoolPage * const parent;
AutoreleasePoolPage *child;
uint32_t const depth;
uint32_t hiwat;
1、AutoreleasePool并沒(méi)有單獨(dú)的結(jié)構(gòu),而是由若干個(gè)AutoreleasePoolPage以雙向鏈表的形式組合而成(分別對(duì)應(yīng)結(jié)構(gòu)中的parent指針和child指針)
2、AutoreleasePool是按線(xiàn)程一一對(duì)應(yīng)的(結(jié)構(gòu)中的thread指針指向當(dāng)前線(xiàn)程)
3、AutoreleasePoolPage每個(gè)對(duì)象會(huì)開(kāi)辟4096字節(jié)內(nèi)存(也就是虛擬內(nèi)存一頁(yè)的大?。?,除了上面的實(shí)例變量所占空間,剩下的空間全部用來(lái)儲(chǔ)存autorelease對(duì)象的地址
4、上面的id *next指針作為游標(biāo)指向棧頂最新add進(jìn)來(lái)的autorelease對(duì)象的下一個(gè)位置,一個(gè)AutoreleasePoolPage的空間被占滿(mǎn)時(shí),會(huì)新建一個(gè)AutoreleasePoolPage對(duì)象,連接鏈表,后來(lái)的autorelease對(duì)象在新的page加入
5、棧中存放的指針指向加入需要release的對(duì)象或者POOL_SENTINEL(哨兵對(duì)象,用于分隔autoreleasepool)。
6、棧中指向POOL_SENTINEL的指針就是autoreleasepool的一個(gè)標(biāo)記。當(dāng)autoreleasepool進(jìn)行出棧操作,每一個(gè)比這個(gè)哨兵對(duì)象后進(jìn)棧的對(duì)象都會(huì)被release。

- 當(dāng) next == begin() 時(shí),表示 AutoreleasePoolPage 為空;
當(dāng) next == end() 時(shí),表示 AutoreleasePoolPage 已滿(mǎn)。
下面來(lái)看看實(shí)現(xiàn)AutoreleasePool的幾個(gè)關(guān)鍵函數(shù)是如何實(shí)現(xiàn)的
AutoreleasePoolPage::push()
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存在且不滿(mǎn)的情況下,直接將需要autorelease的對(duì)象加入棧中
} else if (page) {
return autoreleaseFullPage(obj, page); // 在這個(gè)page已經(jīng)滿(mǎn)了的情況下,新建一個(gè)page并將obj對(duì)象放入新的page(即入棧)
} else {
return autoreleaseNoPage(obj); // 在沒(méi)有page的情況下,新建一個(gè)page并將obj對(duì)象放入新的page(即入棧)
}
}
autoreleaseFullPage(obj, page)和autoreleaseNoPage(obj)的區(qū)別在于autoreleaseFullPage(obj, page)會(huì)將當(dāng)前page的child指向新建的page,而autoreleaseNoPage(obj)會(huì)在新建的page中先入棧一個(gè)POOL_SENTINEL(哨兵對(duì)象),再將obj入棧。
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;
}
AutoreleasePoolPage::pop(ctxt)
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();
}
}
}
AutoreleasePoolPage::autorelease((id)this);
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;
}
autorelease函數(shù)和push函數(shù)一樣,關(guān)鍵代碼都是調(diào)用autoreleaseFast函數(shù)向自動(dòng)釋放池的鏈表?xiàng)V刑砑右粋€(gè)對(duì)象,不過(guò)push函數(shù)的入棧的是一個(gè)哨兵對(duì)象,而autorelease函數(shù)入棧的是需要加入autoreleasepool的對(duì)象。
六、補(bǔ)充
使用容器的block版本的枚舉器時(shí),內(nèi)部會(huì)自動(dòng)添加一個(gè)AutoreleasePool:
[array enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
// 這里被一個(gè)局部@autoreleasepool包圍著
}];
普通for循環(huán)和for in循環(huán)中沒(méi)有,for循環(huán)中遍歷產(chǎn)生大量autorelease變量時(shí),就需要手加局部AutoreleasePool。
-
下面三種情況下是需要我們手動(dòng)添加 autoreleasepool 的:
1、你編寫(xiě)的程序不是基于 UI 框架的,比如說(shuō)命令行工具;
2、你編寫(xiě)的循環(huán)中創(chuàng)建了大量的臨時(shí)對(duì)象;
3、你創(chuàng)建了一個(gè)輔助線(xiàn)程。
參考鏈接
http://blog.sunnyxx.com/2014/10/15/behind-autorelease/
http://blog.leichunfeng.com/blog/2015/05/31/objective-c-autorelease-pool-implementation-principle/#jtss-tsina