【原創(chuàng)博文亚隙,轉(zhuǎn)載請注明出處!】
寫在最前面:你們別光看啊违崇,發(fā)現(xiàn)我說的不對的地方請指出或留言阿弃,希望我們一起進步。
iOS程序的main()函數(shù)我們都很熟悉羞延,在函數(shù)入口處有一個自動釋放池autoreleasepool渣淳,今天我們從這里開始探究autoreleasepool究竟是何方神圣??
int main(int argc, char * argv[]) {
@autoreleasepool {
NSLog(@"Hello, AutoreleasePool!");
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
借助clang轉(zhuǎn)換為C++代碼實現(xiàn)main.cpp文件,窺探一下這個main()函數(shù):
int main(int argc, char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
NSLog((NSString *)&__NSConstantStringImpl__var_folders__6_s9x0n6313d99yqk5pltzp6ym0000gn_T_main_301f30_mi_0);
return UIApplicationMain(argc, argv, __null, NSStringFromClass(((Class (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("AppDelegate"), sel_registerName("class"))));
}
}
發(fā)現(xiàn)@autoreleasepool對應(yīng)的是一個__AtAutoreleasePool類型的變量__autoreleasepool伴箩;在main.cpp文件中找到__AtAutoreleasePool的定義如下:
struct __AtAutoreleasePool {
__AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();}
~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);}
void * atautoreleasepoolobj;
};
可見__AtAutoreleasePool是一個C++結(jié)構(gòu)體入愧,在C++中結(jié)構(gòu)體類似我們iOS中的“類”這個概念,結(jié)構(gòu)體里面有兩個與結(jié)構(gòu)體同名的函數(shù)__AtAutoreleasePool()嗤谚、 ~__AtAutoreleasePool()
分別稱之為構(gòu)造函數(shù)和析構(gòu)函數(shù)棺蛛,他們分別在結(jié)構(gòu)體創(chuàng)建和銷毀的時候調(diào)用,功能類似于iOS中的- (void)init - (void)dealloc
巩步。
因此__autoreleasepool對象一創(chuàng)建就會調(diào)用objc_autoreleasePoolPush();
旁赊,銷毀的時候則調(diào)用objc_autoreleasePoolPop(atautoreleasepoolobj);
,知道了這些椅野,我們沿著objc_autoreleasePoolPush(); objc_autoreleasePoolPush();
這兩個函數(shù)的實現(xiàn)繼續(xù)往下看就好了(從哪里看终畅?可以從蘋果開源的objc源碼中獲燃琛)。
我將源碼中相關(guān)調(diào)用函數(shù)摘抄如下(不摘抄怎么叫“讀源碼”呢??):
void *_objc_autoreleasePoolPush(void)
{
return objc_autoreleasePoolPush();
}
void *objc_autoreleasePoolPush(void)
{
return AutoreleasePoolPage::push();
}
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;
}
id *autoreleaseNewPage(id obj)
{
AutoreleasePoolPage *page = hotPage();
if (page) return autoreleaseFullPage(obj, page);
else return autoreleaseNoPage(obj);
}
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);
}
}
泛看下來离福,這其中涉及到一個重要的類AutoreleasePoolPage杖狼,從源碼中發(fā)現(xiàn)AutoreleasePoolPage有以下7個成員及一些內(nèi)部函數(shù)(已省略):
- 每個AutoreleasePoolPage對象占用4096字節(jié)內(nèi)存,除了用來存放它內(nèi)部的成員變量术徊,剩下的空間用來存放autorelease對象的地址本刽。
- 所有的AutoreleasePoolPage對象通過雙向鏈表的形式連接在一起。
autoreleasePool以autoreleasePoolPage為單位進行展開的赠涮,因為每一個autoreleasePoolPage只有4096 byte子寓,如果自動釋放池中的對象太多就需要分頁(n個autoreleasePoolPage)存儲。當(dāng)開啟一個自動釋放池的時候笋除,執(zhí)行push()函數(shù)中autoreleaseNewPage(POOL_BOUNDARY);
斜友,將一個POOL_BOUNDARY
入棧自動釋放池并返回POOL_BOUNDARY在池中指針。(POOL_BOUNDARY是一個系統(tǒng)定義的宏垃它,其定義為# define POOL_BOUNDARY nil
)鲜屏。自動釋放池結(jié)束的時候調(diào)用pop(void ctxt)函數(shù),同時傳入這個POOL_BOUNDARY
地址国拇,這個時候會從最后一個進棧的對象開始發(fā)送release消息洛史,直到遇到這個POOL_BOUNDARY。自動釋放池內(nèi)部可以多層嵌套酱吝,形如下面這樣(demo運行環(huán)境為MRC*):
多層嵌套之后push()也會多次調(diào)用并將POOL_BOUNDARY壓入棧中也殖,每次返回一個新的存放POOL_BOUNDARY的指針,每一個push()都有與之對應(yīng)的pop(void *ctxt)函數(shù)务热。
多層嵌套的C++代碼:
demo中選擇了4層@autoreleasepool,借助私有函數(shù)void _objc_autoreleasePoolPrint(void);
可以獲取自動釋放池的狀態(tài)崎岂。我們看到自動釋放池中有7個待釋放的對象捆毫,其中__NSArray與__NSSetI是系統(tǒng)的,最后一個TestObject是我們自己創(chuàng)建的冲甘,中間四個POOL對象就是@autoreleasepool 4次绩卤,累計調(diào)用4次push()函數(shù),每次push()之后都會將一個POOL_BOUNDARY壓入池子中的對象江醇。
前面說過autoreleasePool以autoreleasePoolPage為單位進行展開的省艳,每個AutoreleasePoolPage對象占用4096字節(jié)內(nèi)存,如果自動釋放池中的對象總大小超過4096 byte嫁审,自動釋放池中的對象太多就需要分頁(n個autoreleasePoolPage)存儲,這里我臨時創(chuàng)建了700個對象赖晶,(700*8 byte = 5600 byte),自動釋放池的狀態(tài)如下:
code:
int main(int argc, char * argv[]) {
@autoreleasepool {
@autoreleasepool {
NSLog(@"11111");
@autoreleasepool {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunused-variable"
for (int i = 0; i < 700; i++) {
TestObject *testObj = [[[TestObject alloc] init] autorelease];
}
#pragma clang diagnostic pop
_objc_autoreleasePoolPrint();
}
NSLog(@"22222");
}
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
pool state
objc[5438]: 705 releases pending.
objc[5438]: [0x7f8a1e003000] ................ PAGE (full) (cold)
objc[5438]: [0x7f8a1e003038] 0x6000009d1a80 __NSArrayI
objc[5438]: [0x7f8a1e003040] 0x600003ffa8f0 __NSSetI
objc[5438]: [0x7f8a1e003048] ################ POOL 0x7f8a1e003048
objc[5438]: [0x7f8a1e003050] ################ POOL 0x7f8a1e003050
objc[5438]: [0x7f8a1e003058] ################ POOL 0x7f8a1e003058
objc[5438]: [0x7f8a1e003060] 0x600001ee4120 TestObject
objc[5438]: [0x7f8a1e003068] 0x600001ee4160 TestObject
.
.
.
objc[5438]: [0x7f8a1e003ff0] 0x600001ee6010 TestObject
objc[5438]: [0x7f8a1e003ff8] 0x600001ee6020 TestObject
objc[5438]: [0x7f8a1e001000] ................ PAGE (hot)
objc[5438]: [0x7f8a1e001038] 0x600001ee6030 TestObject
objc[5438]: [0x7f8a1e001040] 0x600001ee6040 TestObject
.
.
objc[5438]: [0x7f8a1e001670] 0x600001ee6ca0 TestObject
objc[5438]: ##############
700個OC對象指針大小超過了一個PAGE的承載量律适,因此系統(tǒng)又重啟了一個新的PAGE辐烂,兩個PAGE 地址及狀態(tài)分別為:
objc[5438]: [0x7f8a1e003000] ................ PAGE (full) (cold)
objc[5438]: [0x7f8a1e001000] ................ PAGE (hot)
第一個PAGE標(biāo)記為full、cold捂贿,“full”表示該PAGE空間已滿纠修,“cold”表示該PAGE當(dāng)前不活躍(可以理解為存滿了,其他的自動釋放對象暫時不會去訪問該PAGE)厂僧;
第二個PAGE標(biāo)記hot扣草,表明當(dāng)前正處在使用狀態(tài),因為還沒有放滿颜屠。
再細看一下PAGE的內(nèi)存地址辰妙,PAGE起始地址0x7f8a1e001000,存放的第一個TestObject地址0x7f8a1e001038甫窟,地址相差56 byte密浑,根據(jù)前面AutoreleasePoolPage這個類的結(jié)構(gòu),里面有7個成員對象粗井, 7*8 byte = 56 byte尔破,匹配。因此對于一個AutoreleasePoolPage而言浇衬,總大小為4096 byte懒构,前56 byte存放AutoreleasePoolPage自己的成員指針,后面的內(nèi)存存儲 autorelease 對象指針耘擂。
既然池子中的對象在池子結(jié)束的時候就會被釋放胆剧,那對于iOS應(yīng)用而言,只要程序不異常退出梳星,因為RunLoop赞赖,main函數(shù)就會一直在運行,@autoreleasepool也就不會結(jié)束冤灾,那程序中的那么多函數(shù)中局部變量怎么釋放的呢前域?顯然不是依賴于main()中的 @autoreleasepool
。
那autorelease對象在什么時機會被調(diào)用release?
這個分情況看:
- 如果是@autoreleasepool中的autorelease對象韵吨,那等autoreleasepool結(jié)束的時候就會調(diào)用對象的release方法匿垄,通過上面demo的結(jié)果,這毫無疑問了归粉。
- 那對于程序中局部函數(shù)內(nèi)部的autorelease對象什么時候被調(diào)用release呢椿疗?
對于第二個問題我之前錯誤地以為局部函數(shù)一旦調(diào)用結(jié)束,局部autorelease對象就立馬被釋放了糠悼。事實并非如此届榄,我用一張圖表示一下:
現(xiàn)象是viewDidLoad中的autorelease對象testObj并沒有在viewDidLoad調(diào)用結(jié)束就立馬釋放,而是在viewWillAppear:
與viewDidAppear:
之間被釋放倔喂,這取決于RunLoop铝条。
實際上iOS在主線程的Runloop中注冊了2個Observer靖苇,第1個Observer監(jiān)聽了kCFRunLoopEntry事件,會調(diào)用objc_autoreleasePoolPush()班缰;
第2個Observer監(jiān)聽了kCFRunLoopBeforeWaiting與kCFRunLoopBeforeExit事件贤壁。kCFRunLoopBeforeWaiting的時候會調(diào)用objc_autoreleasePoolPop()、objc_autoreleasePoolPush()埠忘;
kCFRunLoopBeforeExit的時候會調(diào)用objc_autoreleasePoolPop()脾拆。
因此程序在即將休眠的時候會集中處理待release的局部對象。
那在ARC下莹妒,函數(shù)中的局部對象又是在什么時機被dealloc的呢名船?
發(fā)現(xiàn)其局部對象確實是馬上釋放了。ARC下局部對象dealloc時機的表現(xiàn)與MRC下autorelease對象不一致动羽,既然剛才已經(jīng)驗證autorelease對象被dealloc的時機依賴于RunLoop包帚,那從“ARC下局部對象的銷毀與MRC下表現(xiàn)不一致的結(jié)果”猜測編譯器并不是在創(chuàng)建對象的時候加了autoRelease方法,而應(yīng)該是在要出局部函數(shù)之前直接調(diào)用了對象的release方法运吓。
ARC為我們做了什么渴邦?(LLVM + Runtime相互協(xié)作的結(jié)果)
在ARC環(huán)境下,系統(tǒng)利用LLVM編譯器自動為我們給對象添加retain拘哨、release谋梭、autorelease等方法。
像弱引用對象這樣的存在是需要運行時runtime倦青,runtime檢測到弱引用對象銷毀的時候瓮床,將弱引用對象清空掉。