ARC下,我們使用@autoreleasepool{}來使用一個AutoreleasePool
void main(int argc, char * argv[]) {
? ? ? ?@autoreleasepool {
? ? ? ?}
}
隨后編譯器將其改寫成下面的樣子(clang -rewrite-objc main.m):
報錯 main.m:9:9: fatal error: 'UIKit/UIKit.h' file not found,
#import <UIKit/UIKit.h>
尷尬!換個命令 xcrun -sdk iphonesimulator clang -rewrite-objc main.m(指定模擬器/SDK)
如果報錯 xcodebuild[3632:229732] [MT] PluginLoading: Required plug-in compatibility UUID E0A62D1F-3C18-4D74-BFE5-A4167D643966 for plug-in at path '~/Library/Application Support/Developer/Shared/Xcode/Plug-ins/CocoaPods.xcplugin' not present in DVTPlugInCompatibilityUUIDs
則Finder?前往這個地址,顯示包內存在Info.plist文件的DVTPlugInCompatibilityUUIDs數組中添加一item,值為E0A62D1F-3C18-4D74-BFE5-A4167D643966
~>
int main(int argc, char * argv[]) {
/* @autoreleasepool */???{ __AtAutoreleasePool __autoreleasepool;
? ? ?}
}
我們寫的@autoreleasepool{}變成了 @autoreleasepool??與 {}兩部分,第一部分@autorelease被注釋,很明顯它的作用對于編譯器只是一個標示。第二部分{}
__AtAutoreleasePool 是一結構體
struct __AtAutoreleasePool {
? ? ?__AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();}
? ? ?~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);}
? ? ? void * atautoreleasepoolobj;
};
__AtAutoreleasePool __autoreleasepool;??這一句聲明,多少有點C++11 中的trivial的身影(可是看到了結構體中的構造和析構函數,一臉懵逼)
構造函數與類名相同,沒有返回值。析構函數與類名相同,前面加上~,沒有返回值和參數,對象銷毀時自動調用。
未定義構造函數,那么編譯器會添加有一個無參的構造函數,而如果定義了構造函數,不會默認生成,如果你還想允許無參構造,就必須顯式的聲明一個
盡管有點詭異,但是構造函數和析構函數還在那擺著呢。以上代碼等價于
void main(int argc, const char * argv[]) {
{
? ? ? void * autoreleasepoolobj = objc_autoreleasePoolPush();
? ? ? objc_autoreleasePoolPop(autoreleasepoolobj);
}
結構體會在初始化時調用 objc_autoreleasePoolPush() 方法,會在析構時(出作用域)調用 objc_autoreleasePoolPop 方法
@autoreleasepool 只是幫助我們少寫了這兩行代碼而已,讓代碼看起來更美觀。我們下面就要依據這兩個方法,展開
void *objc_autoreleasePoolPush(void) {
? ? ? return AutoreleasePoolPage::push();
}
void objc_autoreleasePoolPop(void *ctxt) {
? ? ? AutoreleasePoolPage::pop(ctxt);
}
AutoreleasePoolPage 的靜態方法push 和 pop,兩個函數都是對AutoreleasePoolPage的簡單封裝
AutoreleasePoolPage is who?
AutoreleasePoolPage是一個C++實現的類
class AutoreleasePoolPage {
? ? ? magic_t const magic;
? ? ? id *next;
? ? ? pthread_t const thread;
? ? ? AutoreleasePoolPage * const parent;
? ? ? AutoreleasePoolPage *child;
? ? ? uint32_t const depth;
? ? ? uint32_t hiwat;
};
1 magic 用于對當前 AutoreleasePoolPage 完整性的校驗
2 AutoreleasePool是按線程一一對應的(結構中的thread指針指向當前線程)
3 AutoreleasePool(自動釋放池)并沒有單獨的結構,而是由若干個AutoreleasePoolPage組合。每一個 AutoreleasePoolPage 的大小都是4096 字節, 前56 bit 用于存儲 AutoreleasePoolPage 的成員變量,剩下的用來存儲加入到自動釋放池中的對象(autorelease對象的地址)
4 上面的next指針作為游標,指向棧頂最新add進來的autorelease對象的下一個位置
next 指向了下一個為空的內存地址,如果 next 指向的地址加入一個 object,它就會如下圖所示移動到下一個為空的內存地址中
5 一個AutoreleasePoolPage的空間被占滿時,會新建一個AutoreleasePoolPage對象,連接鏈表,后來的autorelease對象在新的page加入
6 AutoreleasePoolPage 是雙向鏈表的形式連接起來的(parent 和 child 用來構造雙向鏈表的指針)
7 begin() 和 end() 這兩個類的實例方法獲取一個AutoreleasePoolPage的存儲autorelease對象地址的區域邊界
到了這里,你可能想要知道 POOL_SENTINEL 到底是什么,還有它為什么在棧中?
#define POOL_SENTINEL nil
POOL_SENTINEL 只是 nil 的別名,是一個哨兵對象。
static inline void *push() {
? ? ? ?return autoreleaseFast(POOL_SENTINEL);
}
static inline id *autoreleaseFast(id obj)
{
? ? ?AutoreleasePoolPage *page = hotPage();
? ? ?//有 hotPage 并且 hotPage 不滿
? ? ?if (page && !page->full()) { ?
? ? ? ? ? //調用 page->add(obj) 方法將對象添加至 AutoreleasePoolPage 的棧中
? ? ? ? ? return page->add(obj);
? ? ? } else if (page) {//有 hotPage 并且hotPage 已滿
? ? ? ? ? ? ?return autoreleaseFullPage(obj, page);
? ? ? } else {
? ? ? ? ?//無 hotPage
? ? ? ? ? ? ? return autoreleaseNoPage(obj); ??
? ? ? }
}
1 objc_autoreleasePoolPush -> AutoreleasePoolPage::push --> autoreleaseFast(POOL_SENTINEL)
因此,調用objc_autoreleasePoolPush,最終調用autoreleaseFast 方法,并傳入哨兵對象 POOL_SENTINEL
2 該函數分三種情況選擇不同的代碼執行(hotPage 可以理解為當前正在使用的 AutoreleasePoolPage)
將哨兵對象添加到hotPage中(page->add 添加對象),對函數整理簡化
id *add(id obj) {
? ? ?id *ret = next;
? ? ?*next = obj;
? ? ? next++;
? ? ? return ret;
}
這個函數其實就是一個壓棧的操作,將對象加入 AutoreleasePoolPage 然后移動棧頂的指針。
autoreleaseFast(POOL_SENTINEL),函數返回的是哨兵對象的地址
然后,順便看下autoreleaseFullPage()與autoreleaseNoPage()函數(page->child指向下一頁page)
static id *autoreleaseFullPage(id obj, AutoreleasePoolPage *page) {
? ? ? ?do {?
? ? ? ? ? ? ?if (page->child) page = page->child;
? ? ? ? ? ? ? else page = new AutoreleasePoolPage(page);
? ? ? ? } while (page->full());
? ? ? ?setHotPage(page);
? ? ? ?return page->add(obj);
}
會從傳入的 page 開始遍歷整個雙向鏈表,直到查找到一個未滿的 AutoreleasePoolPage,否則使用構造器傳入 parent 創建一個新的 AutoreleasePoolPage。得到一個可用的 AutoreleasePoolPage 之后,會將該頁面標記成 hotPage,然后調動上面分析過的 page->add 方法添加對象
如果不存在 hotPage,就會調用 autoreleaseNoPage 方法初始化一個 AutoreleasePoolPage,從頭開始構建雙向鏈表,也就是說,新的 AutoreleasePoolPage 是沒有 parent 指針的
static id *autoreleaseNoPage(id obj) {
? ? ? AutoreleasePoolPage *page = new AutoreleasePoolPage(nil);
? ? ? setHotPage(page);
? ? ? if (obj != POOL_SENTINEL) {
//可見成員變量之上必然是哨兵對象,無論傳入與否。來確保在 pop 調用的時候,不會出現異常
? ? ? ? ? ? ?page->add(POOL_SENTINEL);
? ? ? ?}
? ? ? ?return page->add(obj);
}
綜上,autoreleaseFast()將哨兵對象添加到hotpage中,并返回哨兵對象的地址
3 ?objc_autoreleasePoolPush之后,autorelease對象必然執行類似操作進棧,紛紛添加到page中,直到objc_autoreleasePoolPop()(參數對應push返回值,哨兵對象的地址)
static inline void pop(void *token) {
? ? //獲取哨兵對象所在page(首地址)
? ? AutoreleasePoolPage *page = pageForPointer(token);
? ? id *stop = (id *)token;
? ? //釋放本次push進的對象
? ? ?page->releaseUntil(stop);
? ? //根據當前頁的不同狀態 kill 掉不同 child 的頁面
? ? if (page->child) {
? ? ? ? if (page->lessThanHalfFull()) {
? ? ? ? ? ? ? ?page->child->kill();
? ? ? ? } else if (page->child->child) { ??
? ? ? ? ? ? ? ?page->child->child->kill();
?? ? ? ? }
? ? ?}
}
pageForPointer 方法主要是通過內存地址的操作,獲取當前指針所在頁的首地址
static AutoreleasePoolPage *pageForPointer(const void *p) {
? ? ?return pageForPointer((uintptr_t)p);
}
static AutoreleasePoolPage *pageForPointer(uintptr_t p) {
? ? ?AutoreleasePoolPage *result;
? ? ? uintptr_t offset = p % SIZE;
? ? ? assert(offset >= sizeof(AutoreleasePoolPage));
? ? ? result = (AutoreleasePoolPage *)(p - offset);
? ? ? result->fastcheck(); ?
? ? ? return result;
}
將指針地址與頁面的大小(如4096)取模,得到指針在page中的偏移量(因為所有的 AutoreleasePoolPage 在內存中都是對齊的)減去偏移量得到page的首地址,最后調用fastCheck() 用來檢查此地址是不是一個 AutoreleasePoolPage(通過檢查成員magic)
回到?pop函數,然后 page->releaseUntil(stop); 哨兵對象所在頁page調用releaseUntil,參數是哨兵對象的地址
void releaseUntil(id *stop) {
? ? ? while (this->next != stop) {
? ? ? ? ? ? ? ?AutoreleasePoolPage *page = hotPage();?
? ? ? ? ? ? ? ? while (page->empty()) {
? ? ? ? ? ? ? ? ? ? ? ? ? page = page->parent; ??
? ? ? ? ? ? ? ? ? ? ? ? ? ?setHotPage(page);
? ? ? ? ? ? ? ? ?}
? ? ? ? ? ? ? ? ? ?page->unprotect();
? ? ? ? ? ? ? ? ? ? id obj = *--page->next; ?
? ? ? ? ? ? ? ? ? ? memset((void*)page->next, SCRIBBLE, sizeof(*page->next));
? ? ? ? ? ? ? ? ? ? page->protect();
? ? ? ? ? ? ? ? ? ?if (obj != POOL_SENTINEL) {
? ? ? ? ? ? ? ? ? ? ? ? ? ? objc_release(obj); ?}
? ? ? ?}
? ? ? ?setHotPage(this);
}
這里,需要明白一點:
可能此次push進page的對象(指針)比較多,哨兵對象所在的page已不是hotpage
但是,由autoreleaseFullPage()可以看出,hotpage必然是哨兵對象所在的page的子節點(子子節點~)
找到當前hotpage,如果已空,通過parent指針遍歷雙向鏈表,找到不空的page作為hotpage(這個就是對應上面的情況,將hotpage清空,然后找到父節點繼續,直到來到哨兵對象地址)
通過page的next指針, memset將內存的內容設置成 SCRIBBLE,然后使用 objc_release 釋放對象,next指針必然前移。繼續循環釋放對象
每次進棧,add位置依據next指針指向,之后next指向后移。進棧依據next指針,出棧也是依據next指針。出棧之后,之后next指向前移。push可能用到多頁page,釋放必然也可能釋放多頁page,因此push進棧涉及到hotpage的變換,pop出棧也涉及到hotpage的切換。
這是一個不斷出棧的過程,只要哨兵對象所在頁page的next指針不指向此哨兵對象,循環繼續
以上,就是源碼分析。我們應該知道:
1 如果存在多個自動釋放池,那么它們在page之間的界限就是哨兵對象
2 自動釋放池的嵌套
@autoreleasepool {//pool 1
? ? ? obj1 = xxx autorelease;
? ? ? ?@autoreleasepool {//pool 2
? ? ? ? ? ? ? ? __autoreleasing obj2 = xxx;
? ? ? ? ? ? ? ? ? ? ? ?@autoreleasepool {//pool 3
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?obj3 = alloc;
? ? ? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? }
? ? ?}
它們在page中的位置,如下(假設同一個page)
next指針指向
pool 3哨兵對象 nil
obj2地址
pool 2哨兵對象 nil
obj1地址
pool 1哨兵對象 nil
加入釋放池,也是依據代碼順序,加入到對應的哨兵對象之后。釋放池廢棄時,也是先依據代碼順序,先釋放最內測的釋放池。當然這僅僅是以一個page為例
我們之前說過,RunLoop每次循環會創建自動釋放池,這個自動釋放必然是很外側的自動釋放池,而我們手動創建的相對于該自動釋放池是相對內側的釋放池,該自動釋放池釋放時,內側的自動釋放池s必然一同釋放(循環)