雖然在ARC時代我們可以完全不知道Autorelease
就能管理好內存雏吭,但在了解Objective-C
內存管理還是十分重要的,在閱讀了書籍和一些干貨并動手驗證之后淮悼,決定總結autoreleasePool
的實現(xiàn)咐低。
什么是autorelease
autoreleasePool
如何實現(xiàn)需要先知道什么是autorelease
?
autorelease
類似于C語言中Automatic variable自動變量袜腥,程序執(zhí)行時见擦,若某自動變量超出其作用域,該自動變量將被自動廢棄羹令。
autorelease何時釋放
面試時提問Objective-C
內存管理基本都會問到autorelease
何時釋放,在沒有使用@autoreleasepool
的情況,autorelease
對象是在當前的runloop
迭代結束時釋放鲤屡。
每個runloop中都會創(chuàng)建一個 autoreleasepool
并在runloop迭代結束進行釋放。
如果是手動創(chuàng)建autoreleasepool
,自己創(chuàng)建Pool并釋放:
// MRC
NSAutoreleasePool *pool = [NSAutoreleasePool alloc] init];
id obj = [NSObject alloc] init];
[obj autorelease];
[pool drain];
// ARC
@autoreleasepool {
id obj = [NSObject alloc] init];
}
Apple
文檔中提到:
@autoreleasepool blocks are more efficient than using an instance of NSAutoreleasePool directly; you can also use them even if you do not use ARC.
不管是MRC
還是ARC
最好使用@autoreleasepool blocks福侈。
@autoreleasepool
上面提到的使用@autoreleasepool
來手動創(chuàng)建并釋放autorelease
@autoreleasepool
使用clang
編譯之后
/* @autoreleasepool */
{
__AtAutoreleasePool __autoreleasepool;
}
@autoreleasepool
被轉轉換成__AtAutoreleasePool
結構體類型
struct __AtAutoreleasePool
{
__AtAutoreleasePool()
{
atautoreleasepoolobj = objc_autoreleasePoolPush();
}
~__AtAutoreleasePool()
{
objc_autoreleasePoolPop(atautoreleasepoolobj);
}
void * atautoreleasepoolobj;
};
可以看到 __AtAutoreleasePool()
構造函數(shù)調用objc_autoreleasePoolPush()
,~__AtAutoreleasePool()
析構函數(shù)調用 objc_autoreleasePoolPop()
在 MRC
中我們使用 NSAutoreleasePool
來創(chuàng)建AutoreleasePool,那么相應的實現(xiàn)如下:
NSAutoreleasePool *pool = [NSAutoreleasePool alloc] init];
// 相當于調用構造函數(shù)也就是 objc_autoreleasePoolPush();
[pool drain];
// 相當于調用析構函數(shù)也就是 objc_autoreleasePoolPop(pool);
objc_autoreleasePoolPush
和 objc_autoreleasePoolPop
是什么呢酒来?
objc_autoreleasePoolPush
和 objc_autoreleasePoolPop
實現(xiàn)需要從runtime源代碼看到,文中的代碼的是最新的objc4-706.tar.gz
在 NSObject.mm
文件中:
實際上是調用AutoreleasePoolPage
的push
和pop
兩個類方法
AutoreleasePoolPage
首先來看一下AutoreleasePoolPage
這個類
# define EMPTY_POOL_PLACEHOLDER ((id*)1)
# define POOL_BOUNDARY nil
static pthread_key_t const key = AUTORELEASE_POOL_KEY;
static uint8_t const SCRIBBLE = 0xA3; // 0xA3A3A3A3 after releasing
static size_t const SIZE =
#if PROTECT_AUTORELEASEPOOL
PAGE_MAX_SIZE; // must be multiple of vm page size
#else
PAGE_MAX_SIZE; // size and alignment, power of 2
#endif
static size_t const COUNT = SIZE / sizeof(id);
magic_t const magic;
id *next;
pthread_t const thread;
AutoreleasePoolPage * const parent;
AutoreleasePoolPage *child;
uint32_t const depth;
uint32_t hiwat;
-
magic
檢查校驗完整性的變量 -
next
指向新加入的autorelease對象下一個位置如下圖:
thread
page當前所在的線程parent
父節(jié)點 指向前一個pagechild
子節(jié)點 指向下一個pagedepth
鏈表的深度,節(jié)點個數(shù)hiwat
high water mark 數(shù)據(jù)容納的一個上限EMPTY_POOL_PLACEHOLDER
空池占位POOL_BOUNDARY
是一個邊界對象nil
,之前的源代碼變量名是POOL_SENTINEL
哨兵對象,用來區(qū)別每個page
即每個AutoreleasePoolPage
邊界PAGE_MAX_SIZE
定義的大小在下圖可以看到:
-
PAGE_MAX_SIZE
= 4096, 為什么是4096呢肪凛?其實就是虛擬內存每個扇區(qū)4096個字節(jié),4K對齊的說法堰汉。 -
COUNT
一個page
里對象數(shù)
在自動釋放池中每一個AutoreleasePoolPage
都是以雙鏈表的形式連接起來的:
parent
指向前一個page
, child
指向下一個page
push
每當自動釋放池調用objc_autoreleasePoolPush
時都會把邊界對象放進棧頂,然后返回邊界對象,用于釋放。
atautoreleasepoolobj = objc_autoreleasePoolPush();
atautoreleasepoolobj
就是返回的邊界對象
push
就是壓棧的操作,先加入邊界對象然后添加A對象在邊界對象之后,下一個B對象壓入A對象之后,就像羽毛球筒放羽毛球一樣
pop
自動釋放池釋放是傳入 push
返回的邊界對象,
objc_autoreleasePoolPop(atautoreleasepoolobj);
然后將邊界對象指向的這一頁 AutoreleasePoolPage
內的對象釋放
@End
總結:
- 自動釋放池是一個個
AutoreleasePoolPage
組成的一個page
是4096字節(jié)大小,每個AutoreleasePoolPage
以雙向鏈表連接起來形成一個自動釋放池 -
pop
時是傳入邊界對象,然后對page
中的對象發(fā)送release
的消息
文章有什么理解不是很到位的希望指出