自動釋放池
自動釋放池的主要底層數(shù)據(jù)結(jié)構(gòu)是:
__AtAutoreleasePool、AutoreleasePoolPage調(diào)用了
autorelease的對象最終都是通過AutoreleasePoolPage對象來管理的__AtAutoreleasePool結(jié)構(gòu)體
struct __AtAutoreleasePool {
__AtAutoreleasePool() { // 構(gòu)造函數(shù),在創(chuàng)建結(jié)構(gòu)體的時候調(diào)用
atautoreleasepoolobj = objc_autoreleasePoolPush();
}
~__AtAutoreleasePool() { // 析構(gòu)函數(shù),在結(jié)構(gòu)體銷毀的時候調(diào)用
objc_autoreleasePoolPop(atautoreleasepoolobj);
}
void * atautoreleasepoolobj;
};
下面將代碼進行轉(zhuǎn)換
@autoreleasepool {
Person *p4 = [[[MJPerson alloc] init] autorelease];
}
將上述代碼轉(zhuǎn)成C++代碼
{
__AtAutoreleasePool __autoreleasepool;
MJPerson *person = ((MJPerson *(*)(id, SEL))(void *)objc_msgSend)((id)((MJPerson *(*)(id, SEL))(void *)objc_msgSend)((id)((MJPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("MJPerson"), sel_registerName("alloc")), sel_registerName("init")), sel_registerName("autorelease"));
}
去除一些不必要的代碼后變成下面這個樣子
{
__AtAutoreleasePool __autoreleasepool;
MJPerson *person = [[[MJPerson alloc] init] autorelease];
}
又因為__AtAutoreleasePool是一個結(jié)構(gòu)體,所以創(chuàng)建時會調(diào)用其構(gòu)造函數(shù)__AtAutoreleasePool(),當離開其作用域后,會調(diào)用其析構(gòu)函數(shù)~__AtAutoreleasePool(),所以上面的代碼又可以轉(zhuǎn)換成下面的代碼
atautoreleasepoolobj = objc_autoreleasePoolPush();
Person *person = [[[Person alloc] init] autorelease];
objc_autoreleasePoolPop(atautoreleasepoolobj);
4.1 自動釋放池
- 自動釋放池的主要底層數(shù)據(jù)結(jié)構(gòu)是:
__AtAutoreleasePool、AutoreleasePoolPage - 調(diào)用了
autorelease的對象最終都是通過AutoreleasePoolPage對象來管理的
源碼分析
- clang重寫
@autoreleasepool - objc4源碼:
NSObject.mm

變量說明
-
magic用來校驗 AutoreleasePoolPage 的結(jié)構(gòu)是否完整 -
next指向最新添加的 autoreleased 對象的下一個位置,初始化時指向 begin() -
thread指向當前線程 -
parent指向父結(jié)點,第一個結(jié)點的 parent 值為 nil -
child指向子結(jié)點,最后一個結(jié)點的 child 值為 nil -
depth代表深度,從 0 開始,往后遞增 1 -
hiwat代表 high water mark
4.2 AutoreleasePoolPage的結(jié)構(gòu)
- 每個
AutoreleasePoolPage對象占用4096字節(jié)內(nèi)存,除了用來存放它內(nèi)部的成員變量,剩下的空間用來存放autorelease對象的地址 - 所有的
AutoreleasePoolPage對象通過雙向鏈表的形式連接在一起

atautoreleasepoolobj = objc_autoreleasePoolPush();
Person *person = [[[Person alloc] init] autorelease];
objc_autoreleasePoolPop(atautoreleasepoolobj);
上圖的執(zhí)行步驟說明
- 調(diào)用
push方法會將一個POOL_BOUNDARY入棧,并且返回其存放的內(nèi)存地址,即返回給atautoreleasepoolobj。 - 調(diào)用
pop方法時傳入一個POOL_BOUNDARY的內(nèi)存地址,會從最后一個入棧的對象開始發(fā)送release消息,直到遇到這個POOL_BOUNDARY -
id *next指向了下一個能存放autorelease對象地址的區(qū)域
代碼例子如下
extern void _objc_autoreleasePoolPrint(void);
int main(int argc, const char * argv[]) {
@autoreleasepool { // r1 = push()
Person *p1 = [[[Person alloc] init] autorelease];
Person *p2 = [[[Person alloc] init] autorelease];
@autoreleasepool { // r2 = push()
for (int i = 0; i < 5; i++) {
Person *p3 = [[[Person alloc] init] autorelease];
}
@autoreleasepool { // r3 = push()
Person *p4 = [[[Person alloc] init] autorelease];
_objc_autoreleasePoolPrint();
} // pop(r3)
} // pop(r2)
} // pop(r1)
return 0;
}
執(zhí)行結(jié)果

- 因為只打印了一個
PAGE,所以說明他們是在同一個AutoreleasePoolPage,只是每次一個新的autoreleasepool,都會插入一個POOL_BOUNDARY。- 每次釋放對象時,都是從后往前釋放,直到遇到
POOL_BOUNDARY為止。
代碼例子二
int main(int argc, const char * argv[]) {
@autoreleasepool { // r1 = push()
@autoreleasepool {
MJPerson *p1 = [[[MJPerson alloc] init] autorelease];
_objc_autoreleasePoolPrint();
}
return 0;
}
}
執(zhí)行結(jié)果

代碼例子三
int main(int argc, const char * argv[]) {
@autoreleasepool { // r1 = push()
MJPerson *p1 = [[[MJPerson alloc] init] autorelease];
MJPerson *p2 = [[[MJPerson alloc] init] autorelease];
@autoreleasepool { // r2 = push()
for (int i = 0; i < 600; i++) {
MJPerson *p3 = [[[MJPerson alloc] init] autorelease];
}
@autoreleasepool { // r3 = push()
MJPerson *p4 = [[[MJPerson alloc] init] autorelease];
_objc_autoreleasePoolPrint();
} // pop(r3)
} // pop(r2)
} // pop(r1)
return 0;
}
執(zhí)行結(jié)果



4.3 Runloop和Autorelease
iOS在主線程的Runloop中注冊了2個Observer
第1個Observer監(jiān)聽了
kCFRunLoopEntry事件,會調(diào)用objc_autoreleasePoolPush()第2個Observer
<1> 監(jiān)聽了kCFRunLoopBeforeWaiting事件,會調(diào)用objc_autoreleasePoolPop()、objc_autoreleasePoolPush()
<2> 監(jiān)聽了kCFRunLoopBeforeExit事件,會調(diào)用objc_autoreleasePoolPop()
4.4 autorelease對象在什么時機會被調(diào)用release
代碼例子如下
- MRC環(huán)境下
- (void)viewDidLoad {
[super viewDidLoad];
// 這個Person什么時候調(diào)用release,是由RunLoop來控制的
// 它可能是在某次RunLoop循環(huán)中,RunLoop休眠之前調(diào)用了release
MJPerson *person = [[[MJPerson alloc] init] autorelease];
NSLog(@"%s", __func__);
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
NSLog(@"%s", __func__);
}
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
NSLog(@"%s", __func__);
}
運行結(jié)果如下

- 得出結(jié)論,
autorelease并不是根據(jù)對象的作用域來決定釋放時機。- 實際上,
autorelease釋放對象的依據(jù)是Runloop,簡單說,runloop就是iOS中的消息循環(huán)機制,當一個runloop結(jié)束時系統(tǒng)才會一次性清理掉被autorelease處理過的對象,其實本質(zhì)上說是在本次runloop迭代結(jié)束時清理掉被本次迭代期間被放到autorelease pool中的對象的。至于何時runloop結(jié)束并沒有固定的duration。- 本次runloop迭代休眠之前調(diào)用了
objc_autoreleasePoolPop()方法,然后調(diào)用release,從而釋放Person對象。
4.5 方法里有局部對象, 出了方法后會立即釋放嗎
- ARC環(huán)境下
- (void)viewDidLoad {
[super viewDidLoad];
Person *person = [[Person alloc] init];
NSLog(@"%s", __func__);
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
NSLog(@"%s", __func__);
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
NSLog(@"%s", __func__);
}

通過打印結(jié)果可知,當
person對象出了其作用域后就銷毀,即系統(tǒng)會在它出作用域的時候,自動調(diào)用其release方法。
五、擴展
既然由runloop來決定對象釋放時機而不是作用域,那么,在一個{}內(nèi)使用循環(huán)大量創(chuàng)建對象就有可能帶來內(nèi)存上的問題,大量對象會被創(chuàng)建而沒有及時釋放,這時候就需要靠我們?nèi)斯さ母深A(yù)autorelease的釋放了。
上文有提到autorelease pool,一旦一個對象被autorelease,則該對象會被放到iOS的一個池:autorelease pool,其實這個pool本質(zhì)上是一個stack,扔到pool中的對象等價于入棧。我們把需要及時釋放掉的代碼塊放入我們生成的autorelease pool中,結(jié)束后清空這個自定義的pool,主動地讓pool清空掉,從而達到及時釋放內(nèi)存的目的。優(yōu)化代碼如下
@autoreleasePool{
//domeSomeThing;
}
什么時候用@autoreleasepool
根據(jù) Apple的文檔 ,使用場景如下:
- 寫基于命令行的的程序時,就是沒有UI框架,如
AppKit等Cocoa框架時。 - 寫循環(huán),循環(huán)里面包含了大量臨時創(chuàng)建的對象。(本文的例子)
- 創(chuàng)建了新的線程。(非
Cocoa程序創(chuàng)建線程時才需要) - 長時間在后臺運行的任務(wù)。
autorelease 機制基于UI framework。因此寫非UI framework的程序時,需要自己管理對象生存周期。autorelease觸發(fā)時機發(fā)生在下一次runloop的時候。因此如何在一個大的循環(huán)里不斷創(chuàng)建autorelease對象,那么這些對象在下一次runloop回來之前將沒有機會被釋放,可能會耗盡內(nèi)存。這種情況下,可以在循環(huán)內(nèi)部顯式使用@autoreleasepool {}將autorelease對象釋放。- 自己創(chuàng)建的線程。
Cocoa的應(yīng)用都會維護自己autoreleasepool。因此,代碼里spawn的線程,需要顯式添加autoreleasepool。注意:如果是使用POSIX API創(chuàng)建線程,而不是NSThread,那么不能使用Cocoa,因為Cocoa只能在多線程(multithreading)狀態(tài)下工作。但可以使用NSThread創(chuàng)建一個馬上銷毀的線程,使得Cocoa進入multithreading狀態(tài)。
什么對象會加入Autoreleasepool中
- 使用
alloc、new、copy、mutableCopy的方法進行初始化時,由系統(tǒng)管理對象,在適當?shù)奈恢胷elease。 - 使用
array會自動將返回值的對象注冊到Autoreleasepool。 -
__weak修飾的對象,為了保證在引用時不被廢棄,會注冊到Autoreleasepool中。 -
id的指針或對象的指針,在沒有顯示指定時會被注冊到Autoleasepool中。