原作者原文鏈接:http://blog.sunnyxx.com/2014/10/15/behind-autorelease/
Autorelease對象什么時候被釋放
在沒有手加Autorelease Pool的情況下,Autorelease對象是在當(dāng)前的runloop迭代結(jié)束時釋放的镊屎,而他能夠釋放的原因是 系統(tǒng)在每個runloop的迭代中都加入了自動釋放池的push和pop获黔。
AutoreleasePoolPage
ARC下绢记,我們使用@autoreleasepool{}來使用一個AutoreleasePool站玄,隨后編譯器將其改寫成下面的樣子:
void *context = objc_autoreleasePoolPush();
// {}中的代碼
objc_autoreleasePoolPop(context);
而這兩個函數(shù)都是對AutoreleasePoolPage的簡單封裝谆沃,所以自動釋放機(jī)制的核心就在于這個類游盲。
AutoreleasePoolPage是一個C++實(shí)現(xiàn)的類
Autorelease對象什么時候被釋放
在沒有手加Autorelease Pool的情況下,Autorelease對象是在當(dāng)前的runloop迭代結(jié)束時釋放的痹兜,而他能夠釋放的原因是 系統(tǒng)在每個runloop的迭代中都加入了自動釋放池的push和pop穆咐。
AutoreleasePoolPage
ARC下,我們使用@autoreleasepool{}來使用一個AutoreleasePool字旭,隨后編譯器將其改寫成下面的樣子:
void *context = objc_autoreleasePoolPush();
// {}中的代碼
objc_autoreleasePoolPop(context);
而這兩個函數(shù)都是對AutoreleasePoolPage的簡單封裝庸娱,所以自動釋放機(jī)制的核心就在于這個類。
AutoreleasePoolPage是一個C++實(shí)現(xiàn)的類
- AutoreleasePool并沒有單獨(dú)的結(jié)構(gòu)谐算,而是由若干個AutoreleasePoolPage以雙向鏈表的形式組合而成(分別對應(yīng)結(jié)構(gòu)中的parent指針和child指針)
- AutoreleasePool是按線程一一對應(yīng)的(結(jié)構(gòu)中的thread指針指向當(dāng)前線程)
- AutoreleasePoolPage每個對象會開辟4096字節(jié)內(nèi)存(也就是虛擬內(nèi)存一頁的大小)归露,除了上面的實(shí)例變量所占空間洲脂,剩下的空間全部用來儲存autorelease對象的地址
- 上面的id *next指針作為游標(biāo)指向棧頂最新add進(jìn)來的autorelease對象的下一個位置
- 一個AutoreleasePoolPage的空間被占滿時,會新建一個AutoreleasePoolPage對象剧包,連接鏈表恐锦,后來的autorelease對象在新的page加入
所以,若當(dāng)前線程中只有一個AutoreleasePoolPage對象疆液,并記錄了很多autorelease對象地址時內(nèi)存如下圖:
圖中的情況一铅,這一頁再加入一個autorelease對象就要滿了(也就是next指針馬上指向棧頂),這時就要執(zhí)行上面說的操作堕油,建立下一頁page對象潘飘,與這一頁鏈表連接完成后肮之,新page的next指針被初始化在棧底(begin的位置),然后繼續(xù)向棧頂添加新對象卜录。
所以戈擒,向一個對象發(fā)送- autorelease消息,就是將這個對象加入到當(dāng)前AutoreleasePoolPage的棧頂next指針指向的位置
釋放時刻
每當(dāng)進(jìn)行一次objc_autoreleasePoolPush調(diào)用時艰毒,runtime向當(dāng)前的AutoreleasePoolPage中add進(jìn)一個哨兵對象筐高,值為0(也就是個nil),那么這一個page就變成了下面的樣子:
objc_autoreleasePoolPush的返回值正是這個哨兵對象的地址丑瞧,被objc_autoreleasePoolPop(哨兵對象)作為入?yún)⒏掏粒谑牵?/p>
- 根據(jù)傳入的哨兵對象地址找到哨兵對象所處的page
- 在當(dāng)前page中,將晚于哨兵對象插入的所有autorelease對象都發(fā)送一次- release消息绊汹,并向回移動next指針到正確位置
- 補(bǔ)充2:從最新加入的對象一直向前清理稽屏,可以向前跨越若干個page,直到哨兵所在的page
剛才的objc_autoreleasePoolPop執(zhí)行后灸促,最終變成了下面的樣子:
嵌套的AutoreleasePool
知道了上面的原理诫欠,嵌套的AutoreleasePool就非常簡單了,pop的時候總會釋放到上次push的位置為止浴栽,多層的pool就是多個哨兵對象而已荒叼,就像剝洋蔥一樣,每次一層典鸡,互不影響被廓。
使用容器的block版本的枚舉器時,內(nèi)部會自動添加一個AutoreleasePool:
[array enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
// 這里被一個局部@autoreleasepool包圍著
}];
當(dāng)然萝玷,在普通for循環(huán)和for in循環(huán)中沒有嫁乘,所以,還是新版的block版本枚舉器更加方便球碉。for循環(huán)中遍歷產(chǎn)生大量autorelease變量時蜓斧,就需要手加局部AutoreleasePool咯。