在前面幾篇文章,說了關於OC中的內存佈局、內存管理方案、以及MRC狀況下的retainCount、retain、release,可是MRC也已是過去式了,此次來講說ARC。 從MRC到ARC的變化 就取決於@autoreleasepool。c++
@autoreleasepool 自動釋放池: 管理內存的池,把不須要的對象放在自動釋放池中,自動釋放(延遲釋放)這個池子內的對象。bash
@autoreleasepool的應用場景:函數
咱們能夠在工程中隨便找個地方打個斷點 po [NSRunLoop currentRunLoop]
oop
也就是說AutoreleasePool建立是在一個RunLoop事件開始以前(push),AutoreleasePool釋放是在一個RunLoop事件即將結束以前(pop)。 AutoreleasePool裏的Autorelease對象的加入是在RunLoop事件中,AutoreleasePool裏的Autorelease對象的釋放是在AutoreleasePool釋放時。 以上內容參考自這位老哥佈局
使用編譯器clang編譯main.m轉化成main.cpp文件(在終端:clang -rewrite-objc main.m)ui
$ cd main.m所在文件夾spa
$ clang -rewrite-objc main.m -o main.cpp命令行
會在原有路徑下生成一個main.cpp,點開以後咱們能夠找到以下代碼:線程
__AtAutoreleasePool
,在當前代碼直接搜索,很容易就能找到下面這些代碼
注意:這裏的~
是C++的析構函數(destructor) 與構造函數相反,當對象脫離其做用域時(例如對象所在的函數已調用完畢),系統自動執行析構函數。翻譯
從上圖咱們能夠找到兩個東西,從名字也很淺顯,一個進池子一個出池子
objc_autoreleasePoolPush
objc_autoreleasePoolPop
結合上面的注意點,我以爲能夠先這麼總結一下
@autoreleasepool,就是把在它做用域(就是"{}")中的代碼,先push進去,而後等這些代碼都幹完活了,再把他們pop出去。
咱們先看一下這個 objc_autoreleasePoolPush
void *
objc_autoreleasePoolPush(void)
{
return AutoreleasePoolPage::push();
}
複製代碼
這裏出現了一個AutoreleasePoolPage,且語法是C++的語法。咱們這裏先看看這個AutoreleasePoolPage是何方神聖。
咱們先看看變量聲明
class 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; // 4096字節 size and alignment, power of 2
#endif
static size_t const COUNT = SIZE / sizeof(id);
magic_t const magic; // 4*4字節 用來校驗 AutoreleasePoolPage 的結構是否完整;
id *next; // 8字節 指向最新添加的 autoreleased 對象的下一個位置,初始化時指向 begin() ;
pthread_t const thread; // 8字節 指向當前線程;
AutoreleasePoolPage * const parent; // 8字節,指向父結點,第一個結點的 parent 值爲 nil ;雙向鏈表上一個節點
AutoreleasePoolPage *child; //8字節, 指向子結點,最後一個結點的 child 值爲 nil ;雙向鏈表下一個節點
uint32_t const depth; // 4字節,表明深度,從 0 開始,日後遞增 1;
uint32_t hiwat; // 4字節 , 表明 high water mark 。
//這幾個加起來是56字節
...
}
複製代碼
void
_objc_autoreleasePoolPrint(void);
複製代碼
已知AutoreleasePoolPage size = 4096,自身佔用56,那麼剩餘空間爲4096-56=4040。 一個NSObject的大小爲8字節,就是說一個AutoreleasePoolPage的剩餘空間還能夠容納4040/8=505個NSObject。 因此就來一波這種操做
void
_objc_autoreleasePoolPrint(void);
int main(int argc, const char * argv[]) {
@autoreleasepool {
for (int i = 0 ; i<505; i++) {
NSObject *obj = [[NSObject new]autorelease];
}
_objc_autoreleasePoolPrint();
}
return 0;
}
複製代碼
而後看一下打印信息
中間省略幾百個NSObject...
那麼問題來了,按照咱們先前的計算,505個不是應該剛恰好裝滿一個PAGE嗎?這第二頁是什麼鬼?明顯就是多出了一個什麼奇怪的東西。 咱們觀察一下這三個地址
objc[32613]: [0x102002000] ................ PAGE (full) (cold) objc[32613]: [0x102002038] ################ POOL 0x102002038 objc[32613]: [0x102002040] 0x100f8d780 NSObject
前兩個地址的區別就在於一個38,這裏是16進制, 因此38在十進制中爲 3*16+8 = 56,因此這一系列的#號爲變量進入pool的起點 第三個地址就是從38變成了40,38要變成40,在16機制中就是須要38中的8再加上8,讓3變成4。 因此就是這 0x102002038 裏面的東西在做怪。那這個又是什麼呢? 這裏我先貼一下部分代碼
id *autoreleaseNoPage(id obj)
{
// "No page" could mean no pool has been pushed
// or an empty placeholder pool has been pushed and has no contents yet
assert(!hotPage());
// 翻譯:pushExtraBoundary 推入一個額外邊界
bool pushExtraBoundary = false;
if (haveEmptyPoolPlaceholder()) {
//當push一個新頁或者第一頁的時候,在push以前須要先push一個poolboundary(邊界)
pushExtraBoundary = true;
}
else if (obj != POOL_BOUNDARY && DebugMissingPools) {
// 異常狀況,拋錯
_objc_inform("MISSING POOLS: (%p) Object %p of class %s "
"autoreleased with no pool in place - "
"just leaking - break on "
"objc_autoreleaseNoPool() to debug",
pthread_self(), (void*)obj, object_getClassName(obj));
objc_autoreleaseNoPool(obj);
return nil;
}
else if (obj == POOL_BOUNDARY && !DebugPoolAllocation) {
// We are pushing a pool with no pool in place,
// and alloc-per-pool debugging was not requested.
// Install and return the empty pool placeholder.
return setEmptyPoolPlaceholder();
}
// Install the first page.
AutoreleasePoolPage *page = new AutoreleasePoolPage(nil);
setHotPage(page);
// Push a boundary on behalf of the previously-placeholder'd pool.
if (pushExtraBoundary) {
page->add(POOL_BOUNDARY);
}
// Push the requested object or pool.
return page->add(obj);
}
複製代碼
這裏解讀一下這段代碼 當咱們push一個對象進來時,沒有page或者page滿了,須要到新的一頁的時候,他就會將pushExtraBoundary設置爲true,在底下會判斷pushExtraBoundary,若是爲true,就會先push一個POOL_BOUNDARY進入page中。
因此這個POOL_BOUNDARY就是咱們上文中的「奇怪的東西」,從字面翻譯咱們能夠理解爲這是一個「邊界」或者叫「邊界符」
這裏給一張圖,能夠更好的理解,加深印象
這篇文章先到這裏,下一篇autoreleasepool將會解析push跟pop