Autoreleasepool相關的內容是在面試中比較容易被問到的。以前呢,談到Autoreleasepool只能粗淺的瞭解到自動釋放池與內存的管理有關,具體是怎麼樣來管理和釋放對象,並無深刻的學習,本文是筆者在深刻學習Autoreleasepool以後的總結和心得,但願對你們有幫助。git
首先咱們從main函數開始,main函數是咱們應用的入口。github
int main(int argc, char * argv[]) { @autoreleasepool { return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); } }
能夠看出,咱們整個iOS應用都是包含在一個自動釋放池中。
並且在如今的ARC環境中,自動釋放池的用法就是這樣子 @autoreleasepool {}
。面試
這個@autoreleasepool{}
你們都會用,咱們的代碼直接寫在這個大括號內便可。咱們代碼中的對象是怎樣加到自動釋放池中的,最後又是怎麼樣被釋放的呢 ?less
咱們要先知道這個@autoreleasepool
究竟是什麼。函數
從網上的一些博客中能夠學到的,在命令行使用clang -rewrite-objc main.m
讓編譯器從新改寫main函數所在的這個文件。固然了這一步我並無操做,直接「盜用」了你們的結果。oop
從上圖中能夠看出,@autoreleasepool
被轉換爲了一個__AtAutoreleasePool
結構體。而後經過在main.cpp中查找找到這個結構體的定義。學習
struct __AtAutoreleasePool { __AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();} ~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);} void * atautoreleasepoolobj; };
這個結構體在初始化是調用objc_autoreleasePoolPush()
,在析構時調用objc_autoreleasePoolPop()
。ui
通過整理,能夠把main函數中實際的代碼應該是相似這樣的。this
int main(int argc, const char * argv[]) { { void * atautoreleasepoolobj = objc_autoreleasePoolPush(); // do whatever you want objc_autoreleasePoolPop(atautoreleasepoolobj); } return 0; }
下面就是正式的經過源碼來學習Autoreleasepool了spa
上面咱們提到了mian函數中的@autoreleasepool
其實最終轉成了objc_autoreleasePoolPush()
和objc_autoreleasePoolPop()
這兩個方法的調用,咱們去源碼中搜一下這兩個函數。
void * objc_autoreleasePoolPush(void) { return AutoreleasePoolPage::push(); }
void objc_autoreleasePoolPop(void *ctxt) { AutoreleasePoolPage::pop(ctxt); }
有源碼能夠看出,這兩個函數就是對AutoreleasePoolPage類的pish和pop方法的封裝,因此咱們來着重看AutoreleasePoolPage類。
簡化一下代碼,先看類的部分屬性
class AutoreleasePoolPage { 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 magic_t const magic; id *next; pthread_t const thread; AutoreleasePoolPage * const parent; AutoreleasePoolPage *child; uint32_t const depth; uint32_t hiwat; };
熟悉鏈表的朋友,看到這個parent和child就差很少能猜出來了,每個自動釋放池實際上是一個雙向鏈表,鏈表的每個結點就是這個AutoreleasePoolPage,每一個AutoreleasePoolPage的大小爲4096字節
#define I386_PGBYTES 4096 #define PAGE_SIZE I386_PGBYTES
若是咱們的一個AutoreleasePoolPage
被初始化在內存的 0x100816000 ~ 0x100817000 中,它在內存中的結構以下:
其中有 56 bit 用於存儲 AutoreleasePoolPage 的成員變量,剩下的 0x100816038 ~ 0x100817000 都是用來存儲加入到自動釋放池中的對象。
begin()
和 end()
這兩個實例方法幫助咱們快速獲取 0x100816038 ~ 0x100817000 這一範圍的邊界地址。
next
指向了下一個爲空的內存地址,若是next
指向的地址加入一個 object
,它就會以下圖所示移動到下一個爲空的內存地址中:
從圖片中咱們能夠看到在AutoreleasePoolPage的棧中出現了一個POOL_SENTINEL
,咱們稱之爲哨兵對象。
#define POOL_SENTINEL nil
其實哨兵對象只是nil的別名,他有啥做用呢 ?
每一個自動釋放池初始化在調用objc_autoreleasePoolPush
的時候,都會把一個POOL_SENTINEL
push到自動釋放池的棧頂,而且返回這個POOL_SENTINEL
的地址。
int main(int argc, const char * argv[]) { { void * atautoreleasepoolobj = objc_autoreleasePoolPush(); // do whatever you want objc_autoreleasePoolPop(atautoreleasepoolobj); } return 0; }
上面這個atautoreleasepoolobj就是一個POOL_SENTINEL
。
能夠看到在調用objc_autoreleasePoolPop時,會傳進去這個地址:
由於自動釋放池是一個雙向鏈表,並且每個page的空間有限,因此會存在當前page已滿的狀況,也就出現了一個自動釋放池跨越幾個page的狀況,因此在release的時候,也要順着鏈表所有清理掉。
static inline void *push() { id *dest; if (DebugPoolAllocation) { // Each autorelease pool starts on a new pool page. dest = autoreleaseNewPage(POOL_BOUNDARY); } else { dest = autoreleaseFast(POOL_BOUNDARY); } assert(dest == EMPTY_POOL_PLACEHOLDER || *dest == POOL_BOUNDARY); return dest; }
經查閱,DebugPoolAllocation是來區別調試模式的,咱們主要看autoreleaseFast這個函數。
static inline id *autoreleaseFast(id obj) { AutoreleasePoolPage *page = hotPage(); if (page && !page->full()) { return page->add(obj); } else if (page) { return autoreleaseFullPage(obj, page); } else { return autoreleaseNoPage(obj); } }
hotPage( )咱們能夠理解爲獲取當前的AutoreleasePoolPage,獲取到當前page以後又根據page是否已滿來區別處理。
有 hotPage 而且當前 page 不滿
有 hotPage 而且當前 page 已滿
無 hotPage
static __attribute__((noinline)) id *autoreleaseFullPage(id obj, AutoreleasePoolPage *page) { // The hot page is full. // Step to the next non-full page, adding a new page if necessary. // Then add the object to that page. assert(page == hotPage()); assert(page->full() || DebugPoolAllocation); do { if (page->child) page = page->child; else page = new AutoreleasePoolPage(page); } while (page->full()); setHotPage(page); return page->add(obj); }
上面代碼中的函數是在page已滿的時候調用,從源碼中能夠看出經過傳入的page遍歷鏈表,直到找到一個未滿的page,若是遍歷到最後一個結點也沒有未滿的,就新建一個new AutoreleasePoolPage(page);
。而且要把找到的知足條件的這個page設置爲hotPage。
咱們看一下pop的源碼,裏面內容不少,咱們精簡了一下。
static inline void pop(void *token) { AutoreleasePoolPage *page = pageForPointer(token); id *stop = (id *)token; page->releaseUntil(stop); if (page->child) { // 不清楚爲何要用下面這個if分類 if (page->lessThanHalfFull()) { page->child->kill(); } else if (page->child->child) { page->child->child->kill(); } } }
經過token調用pageForPointer()
方法獲取到當前的AutoreleasePoolPage,而後調用releaseUntil()
釋放page中的對象,直到stop,child節點調用kill()
方法。
void kill() { AutoreleasePoolPage *page = this; //經過循環先找到最後一個節點 while (page->child) page = page->child; AutoreleasePoolPage *deathptr; //經過do-while循環,依次從後往前置爲nil do { deathptr = page; page = page->parent; if (page) { page->unprotect(); page->child = nil; page->protect(); } delete deathptr; } while (deathptr != this); }
pageForPointer()
主要是經過內存地址的操做,獲取當前指針所在頁的首地址, releaseUntil()
也是經過一個循環來釋放全部的對象,具體的源碼你們能夠本身看一下。
在沒有手動加AutoreleasePool的狀況下,Autorelease對象都是在當前的runloop迭代結束時釋放的,由於系統在每一個runloop迭代中都加入了自動釋放池Push和Pop。
這個問題又要跟runloop聯繫到一塊兒了,等咱們研究過runloop的源碼,對這個問題應該就有更深入的認識了。