@autoreleasepool {
// Code benefitting from a local autorelease pool.
}
1、原理分析
1.1、__AtAutoreleasePool
下面我們先通過macOS工程來分析@autoreleasepool的底層原理。 macOS工程中的main()函數(shù)什么都沒做,只是放了一個@autoreleasepool。
int main(int argc, const char * argv[]) {
@autoreleasepool {}
return 0;
}
通過 Clang clang -rewrite-objc main.m 將以上代碼轉(zhuǎn)換為 C++ 代碼。
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;
};
int main(int argc, const char * argv[]) {
/* @autoreleasepool */
{ __AtAutoreleasePool __autoreleasepool; }
return 0;
}
可以看到:
@autoreleasepool底層是創(chuàng)建了一個__AtAutoreleasePool結(jié)構(gòu)體對象;在創(chuàng)建
__AtAutoreleasePool結(jié)構(gòu)體時會在構(gòu)造函數(shù)中調(diào)用objc_autoreleasePoolPush()函數(shù),并返回一個atautoreleasepoolobj(POOL_BOUNDARY存放的內(nèi)存地址,下面會講到);在釋放
__AtAutoreleasePool結(jié)構(gòu)體時會在析構(gòu)函數(shù)中調(diào)用objc_autoreleasePoolPop()函數(shù),并將atautoreleasepoolobj傳入。
1.2、AutoreleasePoolPage
下面我們進入Runtime objc4源碼查看以上提到的兩個函數(shù)的實現(xiàn)。
// NSObject.mm
void * objc_autoreleasePoolPush(void)
{
return AutoreleasePoolPage::push();
}
void objc_autoreleasePoolPop(void *ctxt)
{
AutoreleasePoolPage::pop(ctxt);
}
可以得知,
objc_autoreleasePoolPush()和objc_autoreleasePoolPop()兩個函數(shù)其實是調(diào)用了AutoreleasePoolPage類的兩個類方法push()和pop()。所以@autoreleasepool底層就是使用AutoreleasePoolPage類來實現(xiàn)的。
自動釋放池的數(shù)據(jù)結(jié)構(gòu)
- 自動釋放池的主要數(shù)據(jù)結(jié)構(gòu)是:
__AtAutoreleasePool、AutoreleasePoolPage; - 調(diào)用了 autorelease的對象最終都是通過
AutoreleasePoolPage對象來管理的;
下面我們來看一下AutoreleasePoolPage類的定義:
class AutoreleasePoolPage
{
# define EMPTY_POOL_PLACEHOLDER ((id*)1) // EMPTY_POOL_PLACEHOLDER:表示一個空自動釋放池的占位符
# define POOL_BOUNDARY nil // POOL_BOUNDARY:哨兵對象
static pthread_key_t const key = AUTORELEASE_POOL_KEY;
static uint8_t const SCRIBBLE = 0xA3; // 用來標(biāo)記已釋放的對象
static size_t const SIZE = // 每個 Page 對象占用 4096 個字節(jié)內(nèi)存
#if PROTECT_AUTORELEASEPOOL // PAGE_MAX_SIZE = 4096
PAGE_MAX_SIZE; // must be muliple of vm page size
#else
PAGE_MAX_SIZE; // size and alignment, power of 2
#endif
static size_t const COUNT = SIZE / sizeof(id); // Page 的個數(shù)
magic_t const magic; // 用來校驗 Page 的結(jié)構(gòu)是否完整
id *next; // 指向下一個可存放 autorelease 對象地址的位置,初始化指向 begin()
pthread_t const thread; // 指向當(dāng)前線程
AutoreleasePoolPage * const parent; // 指向父結(jié)點,首結(jié)點的 parent 為 nil
AutoreleasePoolPage *child; // 指向子結(jié)點,尾結(jié)點的 child 為 nil
uint32_t const depth; // Page 的深度,從 0 開始遞增
uint32_t hiwat;
......
}
整個程序運行過程中,可能會有多個AutoreleasePoolPage對象。從它的定義可以得知:
自動釋放池(即所有的
AutoreleasePoolPage對象)是以棧為結(jié)點通過雙向鏈表的形式組合而成;自動釋放池與線程一一對應(yīng);
每個
AutoreleasePoolPage對象占用4096字節(jié)內(nèi)存,其中56個字節(jié)用來存放它內(nèi)部的成員變量,剩下的空間(4040個字節(jié))用來存放autorelease對象的地址。
其內(nèi)存分布圖如下:

1.2.1、POOL_BOUNDARY
在分析這些方法之前,先介紹一下POOL_BOUNDARY。
-
POOL_BOUNDARY的前世叫做POOL_SENTINEL,稱為哨兵對象或者邊界對象; -
POOL_BOUNDARY用來區(qū)分不同的自動釋放池,以解決自動釋放池嵌套的問題; - 每當(dāng)創(chuàng)建一個自動釋放池,就會調(diào)用
push()方法將一個POOL_BOUNDARY入棧,并返回其存放的內(nèi)存地址; - 當(dāng)往自動釋放池中添加
autorelease對象時,將autorelease對象的內(nèi)存地址入棧,它們前面至少有一個POOL_BOUNDARY; - 當(dāng)銷毀一個自動釋放池時,會調(diào)用
pop()方法并傳入一個POOL_BOUNDARY,會從自動釋放池中最后一個對象開始,依次給它們發(fā)送release消息,直到遇到這個POOL_BOUNDARY。
1.2.2、push
static inline void *push()
{
id *dest;
if (DebugPoolAllocation) { // 出錯時進入調(diào)試狀態(tài)
// Each autorelease pool starts on a new pool page.
dest = autoreleaseNewPage(POOL_BOUNDARY);
} else {
dest = autoreleaseFast(POOL_BOUNDARY); // 傳入 POOL_BOUNDARY 哨兵對象
}
assert(dest == EMPTY_POOL_PLACEHOLDER || *dest == POOL_BOUNDARY);
return dest;
}
當(dāng)創(chuàng)建一個自動釋放池時,會調(diào)用push()方法。push()方法中調(diào)用了autoreleaseFast()方法并傳入了POOL_BOUNDARY哨兵對象。
下面我們來看一下autoreleaseFast()方法的實現(xiàn):
static inline id *autoreleaseFast(id obj)
{
AutoreleasePoolPage *page = hotPage(); // 雙向鏈表中的最后一個 Page
if (page && !page->full()) { // 如果當(dāng)前 Page 存在且未滿
return page->add(obj); // 將 autorelease 對象入棧,即添加到當(dāng)前 Page 中;
} else if (page) { // 如果當(dāng)前 Page 存在但已滿
return autoreleaseFullPage(obj, page); // 創(chuàng)建一個新的 Page,并將 autorelease 對象添加進去
} else { // 如果當(dāng)前 Page 不存在,即還沒創(chuàng)建過 Page
return autoreleaseNoPage(obj); // 創(chuàng)建第一個 Page,并將 autorelease 對象添加進去
}
}
autoreleaseFast()中先是調(diào)用了hotPage()方法獲得未滿的Page,從AutoreleasePoolPage類的定義可知,每個Page的內(nèi)存大小為 4096個字節(jié),每當(dāng)Page滿了的時候,就會創(chuàng)建一個新的Page。hotPage()方法就是用來獲得這個新創(chuàng)建的未滿的Page。
autoreleaseFast()在執(zhí)行過程中有三種情況:
- ① 當(dāng)前
Page存在且未滿時,通過page->add(obj)將autorelease對象入棧,即添加到當(dāng)前Page中;
② 當(dāng)前Page存在但已滿時,通過autoreleaseFullPage(obj, page)創(chuàng)建一個新的Page,并將autorelease對象添加進去;
③ 當(dāng)前Page不存在,即還沒創(chuàng)建過Page,通過autoreleaseNoPage(obj)創(chuàng)建第一個Page,并將autorelease對象添加進去。
1.2.3、pop
static inline void pop(void *token)
{
AutoreleasePoolPage *page;
id *stop;
if (token == (void*)EMPTY_POOL_PLACEHOLDER) {
// Popping the top-level placeholder pool.
if (hotPage()) {
// Pool was used. Pop its contents normally.
// Pool pages remain allocated for re-use as usual.
pop(coldPage()->begin());
} else {
// Pool was never used. Clear the placeholder.
setHotPage(nil);
}
return;
}
page = pageForPointer(token);
stop = (id *)token;
if (*stop != POOL_BOUNDARY) {
if (stop == page->begin() && !page->parent) {
// Start of coldest page may correctly not be POOL_BOUNDARY:
// 1. top-level pool is popped, leaving the cold page in place
// 2. an object is autoreleased with no pool
} else {
// Error. For bincompat purposes this is not
// fatal in executables built with old SDKs.
return badPop(token);
}
}
if (PrintPoolHiwat) printHiwat();
page->releaseUntil(stop);
// memory: delete empty children
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();
}
}
}
pop()方法的傳參token即為POOL_BOUNDARY對應(yīng)在Page中的地址。當(dāng)銷毀自動釋放池時,會調(diào)用pop()方法將自動釋放池中的autorelease對象全部釋放(實際上是從自動釋放池的中的最后一個入棧的autorelease對象開始,依次給它們發(fā)送一條release消息,直到遇到這個POOL_BOUNDARY)。pop()方法的執(zhí)行過程如下:
- ① 判斷
token是不是EMPTY_POOL_PLACEHOLDER,是的話就清空這個自動釋放池; - ② 如果不是的話,就通過
pageForPointer(token)拿到token所在的Page(自動釋放池的首個Page); - ③ 通過
page->releaseUntil(stop)將自動釋放池中的autorelease對象全部釋放,傳參stop即為POOL_BOUNDARY的地址; - ④ 判斷當(dāng)前
Page是否有子Page,有的話就銷毀。
1.2.4、begin、end、empty、full
下面再來看一下begin、end、empty、full這些方法的實現(xiàn)。
-
begin的地址為:Page自己的地址+Page對象的大小56個字節(jié); -
end的地址為:Page自己的地址+4096個字節(jié); -
empty判斷Page是否為空的條件是next地址是不是等于begin; -
full判斷Page是否已滿的條件是next地址是不是等于end(棧頂)。
id * begin() {
return (id *) ((uint8_t *)this+sizeof(*this));
}
id * end() {
return (id *) ((uint8_t *)this+SIZE);
}
bool empty() {
return next == begin();
}
bool full() {
return next == end();
}
2、查看自動釋放池的情況
可以通過以下私有函數(shù)來查看自動釋放池的情況:
extern void _objc_autoreleasePoolPrint(void);
3、iOS 工程示例分析
在iOS工程中,方法里的autorelease對象是什么時候釋放的呢?
有系統(tǒng)干預(yù)釋放和手動干預(yù)釋放兩種情況。
- 系統(tǒng)干預(yù)釋放是不指定
@autoreleasepool,所有autorelease對象都由主線程的RunLoop創(chuàng)建的@autoreleasepool來管理。 - 手動干預(yù)釋放就是將
autorelease對象添加進我們手動創(chuàng)建的@autoreleasepool中。
下面還是在MRC環(huán)境下進行分析。
3.1、系統(tǒng)干預(yù)釋放
我們先來看以下 Xcode 11 版本的iOS程序中的main()函數(shù),和舊版本的差異。
// Xcode 11
int main(int argc, char * argv[]) {
NSString * appDelegateClassName;
@autoreleasepool {
// Setup code that might create autoreleased objects goes here.
appDelegateClassName = NSStringFromClass([AppDelegate class]);
}
return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}
// Xcode 舊版本
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
新版本 Xcode 11 中的 main 函數(shù)發(fā)生了哪些變化?
舊版本是將整個應(yīng)用程序運行放在@autoreleasepool內(nèi),由于RunLoop的存在,要return即程序結(jié)束后@autoreleasepool作用域才會結(jié)束,這意味著程序結(jié)束后main函數(shù)中的@autoreleasepool中的autorelease對象才會釋放。
而在 Xcode 11中,觸發(fā)主線程RunLoop的UIApplicationMain函數(shù)放在了@autoreleasepool外面,這可以保證@autoreleasepool中的autorelease對象在程序啟動后立即釋放。正如新版本的@autoreleasepool中的注釋所寫 “Setup code that might create autoreleased objects goes here.”(如上代碼),可以將autorelease對象放在此處。
接著我們來看 “系統(tǒng)干預(yù)釋放” 情況的示例:
- (void)viewDidLoad {
[super viewDidLoad];
Person *person = [[[Person 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__);
}
// -[ViewController viewDidLoad]
// -[ViewController viewWillAppear:]
// -[Person dealloc]
// -[ViewController viewDidAppear:]
可以看到,調(diào)用了autorelease方法的person對象不是在viewDidLoad方法結(jié)束后釋放,而是在viewWillAppear方法結(jié)束后釋放,說明在viewWillAppear方法結(jié)束的時候,調(diào)用了pop()方法釋放了person對象。
其實這是由RunLoop控制的,下面來講解一下RunLoop和@autoreleasepool的關(guān)系。
3.2、RunLoop 與 @autoreleasepool
iOS在主線程的RunLoop中注冊了兩個Observer。
第1個Observer
- 監(jiān)聽了
kCFRunLoopEntry事件,會調(diào)用objc_autoreleasePoolPush();
第2個Observer
- ① 監(jiān)聽了
kCFRunLoopBeforeWaiting事件,會調(diào)用objc_autoreleasePoolPop()、objc_autoreleasePoolPush(); - ② 監(jiān)聽了
kCFRunLoopBeforeExit事件,會調(diào)用objc_autoreleasePoolPop()。

所以,在iOS工程中系統(tǒng)干預(yù)釋放的autorelease對象的釋放時機是由RunLoop控制的,會在當(dāng)前RunLoop每次循環(huán)結(jié)束時釋放。以上person對象在viewWillAppear方法結(jié)束后釋放,說明viewDidLoad和viewWillAppear方法在同一次循環(huán)里。
-
kCFRunLoopEntry:在即將進入RunLoop時,會自動創(chuàng)建一個__AtAutoreleasePool結(jié)構(gòu)體對象,并調(diào)用objc_autoreleasePoolPush()函數(shù)。
-kCFRunLoopBeforeWaiting:在RunLoop即將休眠時,會自動銷毀一個__AtAutoreleasePool對象,調(diào)用objc_autoreleasePoolPop()。然后創(chuàng)建一個新的__AtAutoreleasePool對象,并調(diào)用objc_autoreleasePoolPush()。 -
kCFRunLoopBeforeExit,在即將退出RunLoop時,會自動銷毀最后一個創(chuàng)建的__AtAutoreleasePool對象,并調(diào)用objc_autoreleasePoolPop()。
3.3、手動干預(yù)釋放
我們再來看一下手動干預(yù)釋放的情況。
- (void)viewDidLoad {
[super viewDidLoad];
@autoreleasepool {
Person *person = [[[Person 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__);
}
// -[Person dealloc]
// -[ViewController viewDidLoad]
// -[ViewController viewWillAppear:]
// -[ViewController viewDidAppear:]
可以看到,添加進手動指定的@autoreleasepool中的autorelease對象,在@autoreleasepool大括號結(jié)束時就會釋放,不受RunLoop控制。