一 .autorelease對(duì)象的釋放時(shí)機(jī)
二.AutoreleasePoolPage對(duì)象
三. autorelease與RunLoop的關(guān)系
一 .autorelease對(duì)象的釋放時(shí)機(jī)
@autoreleasepool
即自動(dòng)釋放池,在自動(dòng)釋放池中聲明的對(duì)象如果調(diào)用了autorelase
方法,那么當(dāng)@autoreleasepool
代碼塊執(zhí)行完之后,在塊中調(diào)用過autorelease
方法的對(duì)象都會(huì)自動(dòng)調(diào)用一次release方法户魏。
比如下面的代碼:
打印結(jié)果:
可以看到打印結(jié)果也驗(yàn)證了我們上面所說的,但是事實(shí)確實(shí)如此么?或者說Person對(duì)象是在執(zhí)行到第23行的時(shí)候調(diào)用了release方法 這個(gè)說法準(zhǔn)確么?
為了更好的說明問題我們?cè)賮砜匆粋€(gè)小例子:
上面的代碼中Person
對(duì)象是在第28行銷毀么?
我們來看一下打印結(jié)果就知道了:
可以看到
Person
對(duì)象并沒有在viewDidLoad
執(zhí)行完銷毀,為什么會(huì)這樣呢?
為了搞明白這些我們有必要來對(duì) autorelease
的內(nèi)部做一些深入的探究.....
通過下面的命令將我們的.m文件轉(zhuǎn)化成.cpp文件
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m
打開main.cpp
文件,拖到最后,我們可以看到生成的C++代碼如下所示:
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ {
__AtAutoreleasePool __autoreleasepool;
Person *per = ((Person *(*)(id, SEL))(void *)objc_msgSend)((id)((Person *(*)(id, SEL))(void *)objc_msgSend)((id)((Person *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Person"), sel_registerName("alloc")), sel_registerName("init")), sel_registerName("autorelease"));
NSLog((NSString *)&__NSConstantStringImpl__var_folders_9n_sqylftm50nv_y8jbf4089_fr0000gn_T_main_885529_mi_1);
}
NSLog((NSString *)&__NSConstantStringImpl__var_folders_9n_sqylftm50nv_y8jbf4089_fr0000gn_T_main_885529_mi_2);
return 0;
}
其實(shí)重點(diǎn)就是 __AtAutoreleasePool
這個(gè)對(duì)象,我們來看看該對(duì)象的結(jié)構(gòu):
struct __AtAutoreleasePool {
//構(gòu)造函數(shù),在創(chuàng)建該結(jié)構(gòu)體的時(shí)候調(diào)用
__AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();}
//析構(gòu)函數(shù),在銷毀該結(jié)構(gòu)體的時(shí)候調(diào)用
//將objc_autoreleasePoolPush()返回的結(jié)果傳入到objc_autoreleasePoolPop()中
~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);}
void * atautoreleasepoolobj;
};
它其實(shí)就是一個(gè)結(jié)構(gòu)體對(duì)象,內(nèi)部包括了一個(gè)構(gòu)造函數(shù)和一個(gè)析構(gòu)函數(shù).
objc_autoreleasePoolPush()
和 objc_autoreleasePoolPop()
又是什么呢太防?
我們可以從Runtime源碼中看到他們的實(shí)現(xiàn)如下:
通過上圖我們可以看到這個(gè)兩個(gè)方法內(nèi)部分別調(diào)用了
AutoreleasePoolPage
對(duì)象的push()
和pop()
方法,所以調(diào)用了autorelease的對(duì)象最終都是通過AutoreleasePoolPage對(duì)象來管理的
2. AutoreleasePoolPage
關(guān)于AutoreleasePoolPage
對(duì)象我們需要知道下面兩點(diǎn):
- 每個(gè)
AutoreleasePoolPage
對(duì)象占用4096
個(gè)字節(jié)的內(nèi)存,除了用來存放它內(nèi)部的成員變量外串纺,剩下的空間用來存放autorelease對(duì)象的地址 - 所有的AutoreleasePoolPage對(duì)象都是通過雙向鏈表的形式連接在一起
內(nèi)部結(jié)構(gòu):
通過源碼我們可以看到AutoreleasePoolPage
對(duì)象的大概結(jié)構(gòu)如下圖所示:
-
id *next
指向了下一個(gè)能存放autorelease對(duì)象地址的區(qū)域 如下圖:
parent
父節(jié)點(diǎn) 指向前一個(gè)page-
child
子節(jié)點(diǎn) 指向下一個(gè)page ,如下圖
POOL_BOUNDARY
是一個(gè)邊界對(duì)象 nil,之前的源代碼變量名是POOL_SENTINEL
哨兵對(duì)象,用來區(qū)別每個(gè)page即每個(gè)AutoreleasePoolPage
邊界
AutoreleasePoolPage::push()
調(diào)用push方法會(huì)將一個(gè)POOL_BOUNDARY
入棧棺耍,并且返回其存放的內(nèi)存地址.
push就是壓棧的操作,先加入邊界對(duì)象,然后添加person1
對(duì)象,然后是person2
對(duì)象...以此類推
AutoreleasePoolPage::pop(ctxt);
調(diào)用pop方法時(shí)傳入一個(gè)POOL_BOUNDARY
的內(nèi)存地址,會(huì)從最后一個(gè)入棧的對(duì)象開始發(fā)送release
消息宏多,直到遇到這個(gè)POOL_BOUNDARY
(因?yàn)槭请p向鏈表,所以可以向上尋找)
3. autorelease與RunLoop
通過以上的分析我們大概了解了autorelease的內(nèi)部結(jié)構(gòu)以及執(zhí)行原理,但是你還是沒有說明使用autorelease的對(duì)象是在什么時(shí)候釋放的啊?別著急 慢慢來....
在應(yīng)用程序剛啟動(dòng)的時(shí)候我們打印當(dāng)前的RunLoop
對(duì)象 (關(guān)于RunLoop
對(duì)象的使用以及分析可以看看我的這篇文章RunLoop的使用)
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"%@",[NSRunLoop currentRunLoop]);
}
部分結(jié)果如下:
由上圖我們可以看到iOS應(yīng)用啟動(dòng)后RunLoop
會(huì)注冊(cè)兩個(gè)Observer來管理和維護(hù)AutoreleasePool
我們知道 RunLoop
有如下這幾種狀態(tài):
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry = (1UL << 0), // 0 進(jìn)入RunLoop
kCFRunLoopBeforeTimers = (1UL << 1), // 2 即將開始Timer處理
kCFRunLoopBeforeSources = (1UL << 2), // 4 即將開始Source處理
kCFRunLoopBeforeWaiting = (1UL << 5), // 32 即將進(jìn)入休眠
kCFRunLoopAfterWaiting = (1UL << 6), // 64 從休眠狀態(tài)喚醒
kCFRunLoopExit = (1UL << 7), // 128 退出RunLoop
kCFRunLoopAllActivities = 0x0FFFFFFFU
};
可以看到 activities = 0x1
監(jiān)測(cè)的是kCFRunLoopEntry
也就是進(jìn)入RunLoop的狀態(tài),此時(shí)它會(huì)回調(diào)objc_autoreleasePoolPush()
方法向當(dāng)前的AutoreleasePoolPage
增加一個(gè)POOL_BOUNDARY
標(biāo)志創(chuàng)建自動(dòng)釋放池疯攒。
而activities = 0xa0
監(jiān)測(cè)的是kCFRunLoopBeforeWaiting
和kCFRunLoopExit
兩種狀態(tài).
在kCFRunLoopBeforeWaiting
(即將進(jìn)入休眠)時(shí)會(huì)調(diào)用objc_autoreleasePoolPop()
和objc_autoreleasePoolPush()
方法. 系統(tǒng)會(huì)根據(jù)情況從最新加入的對(duì)象一直往前清理直到遇到POOL_BOUNDARY
標(biāo)志
而在即將退出RunLoop時(shí)會(huì)調(diào)用objc_autoreleasePoolPop()
方法釋放自動(dòng)釋放池內(nèi)對(duì)象嗦随。
所以autorelease的釋放時(shí)機(jī)取決于RunLoop的運(yùn)行狀態(tài).