一、AutoreleasePool是什么
AutoreleasePool(自動釋放池)是OC中的一種內存自動回收機制,它可以延遲加入AutoreleasePool中的變量release的時機。在正常情況下,創建的變量會在超出其作用域的時候release,但是如果將變量加入AutoreleasePool,那么release將延遲執行。
看到這里有人可能會問,那到底延遲到什么時候執行呢?看完本文后,各位心中自然會有答案。
讓我們寫個Demo來驗證一下:
#import <Foundation/Foundation.h>
// 生成兩個全局weak變量用來觀察實驗對象
__weak NSString *weak_String;
__weak NSString *weak_StringAutorelease;
void createString(void) {
NSString *string = [[NSString alloc] initWithFormat:@"Hello, World!"]; // 創建常規對象
NSString *stringAutorelease = [NSString stringWithFormat:@"Hello, World! Autorelease"]; // 創建autorelease對象
weak_String = string;
weak_StringAutorelease = stringAutorelease;
NSLog(@"------in the createString()------");
NSLog(@"%@", weak_String);
NSLog(@"%@\n\n", weak_StringAutorelease);
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
createString();
NSLog(@"------in the autoreleasepool------");
NSLog(@"%@", weak_String);
NSLog(@"%@\n\n", weak_StringAutorelease);
}
NSLog(@"------in the main()------");
NSLog(@"%@", weak_String);
NSLog(@"%@", weak_StringAutorelease);
return 0;
}
上述代碼運行結果如下:
2016-04-01 16:21:46.961 AutoreleasePool[18401:708414] ------in the createString()------
2016-04-01 16:21:46.962 AutoreleasePool[18401:708414] Hello, World!
2016-04-01 16:21:46.962 AutoreleasePool[18401:708414] Hello, World! Autorelease
2016-04-01 16:21:46.962 AutoreleasePool[18401:708414] ------in the autoreleasepool------
2016-04-01 16:21:46.962 AutoreleasePool[18401:708414] (null)
2016-04-01 16:21:46.962 AutoreleasePool[18401:708414] Hello, World! Autorelease
2016-04-01 16:21:46.962 AutoreleasePool[18401:708414] ------in the main()------
2016-04-01 16:21:46.962 AutoreleasePool[18401:708414] (null)
2016-04-01 16:21:46.962 AutoreleasePool[18401:708414] (null)
Program ended with exit code: 0
首先在createString函數中創建了一個常規NSString對象和一個autorelease對象,然后分別賦值給兩個weak全局變量用于觀察目標對象。通過兩個weak全局變量的打印結果我們可以看到,在createString
函數中兩個對象都是正常存在的,出了createString
函數在autoreleasepool中,常規對象已經被釋放,而autorelease對象依然存在。在autoreleasepool外,autorelease對象也被釋放了。
通過運行結果,我們已經直觀的了解了AutoreleasePool的作用,那么AutoreleasePool是如何實現的呢?
二、AutoreleasePool的實現
接下來我們將一步步探尋AutoreleasePool的底層實現:
首先我們調整上面的代碼,只留下main函數和@autoreleasepool{}。
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSLog(@"Hello, World!");
}
return 0;
}
然后在終端中使用clang -rewrite-objc
命令將上述OC代碼重寫成C++的實現。
搜索main我們可以看到main函數的實現重寫成了如下代碼:
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
NSLog((NSString *)&__NSConstantStringImpl__var_folders_kb_06b822gn59df4d1zt99361xw0000gn_T_main_d39a79_mi_0);
}
return 0;
}
通過對比可以發現,蘋果通過聲明一個__AtAutoreleasePool
類型的局部變量__autoreleasepool
實現了@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;
};
根據構造函數和析構函數的特點(自動局部變量的構造函數是在程序執行到聲明這個對象的位置時調用的,而對應的析構函數是在程序執行到離開這個對象的作用域時調用),我們可以將上面兩段代碼簡化成如下形式:
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ {
void *atautoreleasepoolobj = objc_autoreleasePoolPush();
NSLog((NSString *)&__NSConstantStringImpl__var_folders_kb_06b822gn59df4d1zt99361xw0000gn_T_main_d39a79_mi_0);
objc_autoreleasePoolPop(atautoreleasepoolobj);
}
return 0;
}
至此,我們可以分析出,單個自動釋放池的執行過程就是objc_autoreleasePoolPush()
—> [object autorelease]
—> objc_autoreleasePoolPop(void *)
。
看到這兩個函數的前綴,我們就知道它們是runtime中的兩個函數,接下來我們就打開runtime的源碼,看看它們是如何實現的。文中使用的源碼是objc4-680.tar.gz
三、AutoreleasePool源碼解析
在runtime項目中搜索objc_autoreleasePoolPush
我們可以在objc/Source/NSObject.mm中的1749~1754行找到objc_autoreleasePoolPush()
函數的實現:
void *
objc_autoreleasePoolPush(void)
{
if (UseGC) return nil;
return AutoreleasePoolPage::push();
}
同樣我們可以找到objc_autoreleasePoolPop()
函數的實現:
void
objc_autoreleasePoolPop(void *ctxt)
{
if (UseGC) return;
AutoreleasePoolPage::pop(ctxt);
}
看到這里,我們發現這兩個函數的實現都是調用了AutoreleasePoolPage
類中的方法。于是我們可以斷定,AutoreleasePool的是通過AutoreleasePoolPage
類來實現的。
打開AutoreleasePoolPage
的定義我們可以看到它有下列屬性:
magic_t const magic;
id *next;
pthread_t const thread;
AutoreleasePoolPage * const parent;
AutoreleasePoolPage *child;
uint32_t const depth;
uint32_t hiwat;
通過這些屬性,我們可以推斷出,這是一個雙向鏈表的節點,AutoreleasePool的內存結構就是一個雙向鏈表。而源碼上的注釋也證實我們的推測:
/***********************************************************************
Autorelease pool implementation
A thread's autorelease pool is a stack of pointers.
Each pointer is either an object to release, or POOL_SENTINEL which is
an autorelease pool boundary.
A pool token is a pointer to the POOL_SENTINEL for that pool. When
the pool is popped, every object hotter than the sentinel is released.
The stack is divided into a doubly-linked list of pages. Pages are added
and deleted as necessary.
Thread-local storage points to the hot page, where newly autoreleased
objects are stored.
**********************************************************************/
一個線程的autoreleasepool就是一個指針棧。
棧中存放的指針指向加入需要release的對象或者POOL_SENTINEL
(哨兵對象,用于分隔autoreleasepool)。
棧中指向POOL_SENTINEL
的指針就是autoreleasepool的一個標記。當autoreleasepool進行出棧操作,每一個比這個哨兵對象后進棧的對象都會release。
這個棧是由一個以page為節點雙向鏈表組成,page根據需求進行增減。
autoreleasepool對應的線程存儲了指向最新page(也就是最新添加autorelease對象的page)的指針。
通過閱讀源碼,我們可以分析出上述屬性的作用:
-
magic
:用來校驗 AutoreleasePoolPage 的結構是否完整; -
next
:指向棧頂,也就是最新入棧的autorelease對象的下一個位置; -
thread
:指向當前線程; -
parent
:指向父節點 -
child
:指向子節點 -
depth
:表示鏈表的深度,也就是鏈表節點的個數 -
hiwat
:表示high water mark(最高水位標記)
接下來我們看看實現AutoreleasePool的幾個關鍵函數是如何實現的。為了方便起見,就直接將注釋添加在代碼中。
AutoreleasePoolPage::push()
static inline void *push()
{
id *dest;
if (DebugPoolAllocation) { // 區別調試模式
// Each autorelease pool starts on a new pool page.
// 調試模式下將新建一個鏈表節點,并將一個哨兵對象添加到鏈表棧中
dest = autoreleaseNewPage(POOL_SENTINEL);
} else {
dest = autoreleaseFast(POOL_SENTINEL); // 添加一個哨兵對象到自動釋放池的鏈表棧中
}
assert(*dest == POOL_SENTINEL);
return dest;
}
static inline id *autoreleaseFast(id obj)
{
AutoreleasePoolPage *page = hotPage(); // 獲取最新的page(即鏈表上最新的節點)
if (page && !page->full()) {
return page->add(obj); // 在這個page存在且不滿的情況下,直接將需要autorelease的對象加入棧中
} else if (page) {
return autoreleaseFullPage(obj, page); // 在這個page已經滿了的情況下,新建一個page并將obj對象放入新的page(即入棧)
} else {
return autoreleaseNoPage(obj); // 在沒有page的情況下,新建一個page并將obj對象放入新的page(即入棧)
}
}
autoreleaseFullPage(obj, page)
和autoreleaseNoPage(obj)
的區別在于autoreleaseFullPage(obj, page)
會將當前page的child指向新建的page,而autoreleaseNoPage(obj)
會在新建的page中先入棧一個POOL_SENTINEL
(哨兵對象),再將obj入棧。
id *add(id obj) // 入棧操作
{
assert(!full());
unprotect(); // 解除保護
id *ret = next; // faster than `return next-1` because of aliasing
*next++ = obj; // 將obj入棧到棧頂并重新定位棧頂
protect(); // 添加保護
return ret;
}
AutoreleasePoolPage::pop(ctxt);
static inline void pop(void *token) // token指針指向棧頂的地址
{
AutoreleasePoolPage *page;
id *stop;
page = pageForPointer(token); // 通過棧頂的地址找到對應的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(); // 記錄最高水位標記
page->releaseUntil(stop); // 從棧頂開始操作出棧,并向棧中的對象發送release消息,直到遇到第一個哨兵對象
// 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();
}
}
}
AutoreleasePoolPage::autorelease((id)this);
static inline id autorelease(id obj)
{
assert(obj);
assert(!obj->isTaggedPointer());
id *dest __unused = autoreleaseFast(obj); // 添加obj對象到自動釋放池的鏈表棧中
assert(!dest || *dest == obj);
return obj;
}
autorelease
函數和push
函數一樣,關鍵代碼都是調用autoreleaseFast
函數向自動釋放池的鏈表棧中添加一個對象,不過push
函數的入棧的是一個哨兵對象,而autorelease
函數入棧的是需要加入autoreleasepool
的對象。
四、補充
上面我們講了AutoreleasePoolPage
的定義的屬性中有一個hiwat
表示high water mark(最高水位標記)。那么什么是最高水位標記呢?這個概念可以用自然界中的潮汐現象來解釋。大家都知道潮水是有漲有落的,漲潮漲到最高時的水位就是最高水位。放在代碼中來說,autoreleasepool的內存結構是一個雙向鏈表棧,會頻繁的有入棧和出棧操作,棧中存放的對象也會有增有減,hiwat
就記錄了入棧對象最多時候對象的個數。
static void printHiwat()
{
// Check and propagate high water mark
// Ignore high water marks under 256 to suppress noise.
AutoreleasePoolPage *p = hotPage(); // 獲取最新的page
uint32_t mark = p->depth*COUNT + (uint32_t)(p->next - p->begin()); // 計算棧中對象的數量
if (mark > p->hiwat && mark > 256) { // 當數量大于當前記錄的最高水位標記且大
for( ; p; p = p->parent) { // 于256,更新每個page中的最高水位標記
p->unprotect();
p->hiwat = mark;
p->protect();
}
// ······
}
}
回到開頭的問題:加入AutoreleasePool的對象,release將延遲到什么時候執行?
相信現在各位心里都已經有答案了。
參考鏈接
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