自動釋放池-AutoReleasePool
自動釋放池是OC中的一種內(nèi)存自動回收機(jī)制莉炉,它可以將加入AutoreleasePool中的變量release的時機(jī)延遲,簡單來說梆暮,就是當(dāng)創(chuàng)建一個對象绍昂,在正常情況下啦粹,變量會在超出其作用域的時立即release窘游。如果將對象加入到了自動釋放池中,這個對象并不會立即釋放贪嫂,會等到runloop休眠/超出autoreleasepool作用域{}之后才會被釋放
1艾蓝、從程序啟動到加載完成,主線程對應(yīng)的runloop會處于休眠狀態(tài)赢织,等待用戶交互來喚醒runloop
2、用戶的每一次交互都會啟動一次runloop于置,用于處理用戶的所有點擊、觸摸事件等
3饱狂、runloop在監(jiān)聽到交互事件后宪彩,就會創(chuàng)建自動釋放池,并將所有延遲釋放的對象添加到自動釋放池中
4尿孔、在一次完整的runloop結(jié)束之前,會向自動釋放池中所有對象發(fā)送release消息活合,然后銷毀自動釋放池
總結(jié)
autoreleasepool其本質(zhì)是一個結(jié)構(gòu)體對象,一個自動釋放池對象就是頁留晚,是是棧結(jié)構(gòu)存儲告嘲,符合先進(jìn)后出的原則即可
頁的棧底是一個56字節(jié)大小的空占位符,一頁總大小為4096字節(jié)
只有第一頁有哨兵對象橄唬,最多存儲504個對象,從第二頁開始最多存儲505個對象
autoreleasepool在加入要釋放的對象時隆判,底層調(diào)用的是objc_autoreleasePoolPush方法
-
autoreleasepool在調(diào)用析構(gòu)函數(shù)釋放時僧界,內(nèi)部的實現(xiàn)是調(diào)用objc_autoreleasePoolPop方法 image.jpeg
對于自動釋放池,我們主要關(guān)心的點有以下三點:
1捂襟、自動釋放池什么時候創(chuàng)建?
2郎汪、對象是如何加入自動釋放池的闯狱?
3、哪些對象才會加入自動釋放池哄孤?
objc_autoreleasePoolPush 源碼分析
判斷是否為有 pool
如果沒有,則通過autoreleaseNewPage方法創(chuàng)建
-
如果有凝危,則通過autoreleaseFast壓棧哨兵對象 image.jpeg
1、創(chuàng)建頁 autoreleaseNewPage
進(jìn)入objc_autoreleasePoolPush -> push -> autoreleaseNewPage源碼實現(xiàn)蛾默,主要是通過hotPage`獲取當(dāng)前頁,判斷當(dāng)前頁是否存在
如果存在冬念,則通過autoreleaseFullPage方法壓棧對象
-
如果不存在,則通過autoreleaseNoPage方法創(chuàng)建頁 image.jpegimage.jpegimage.jpegimage.jpeg
2、壓棧對象 autoreleaseFast
進(jìn)入autoreleaseFast源碼据块,主要有以下幾步:
獲取當(dāng)前操作頁折剃,并判斷頁是否存在以及是否滿了
如果頁存在,且未滿怕犁,則通過add方法壓棧對象
如果頁存在,且滿了戈轿,則通過autoreleaseFullPage方法安排新的頁面
-
如果頁不存在阵子,則通過autoreleaseNoPage方法創(chuàng)建新頁 image.jpegimage.jpegimage.jpeg
3挠进、autorelease 底層分析
如果是對象领突,則調(diào)用對象的autorelease進(jìn)行釋放
如果不是對象 或者 是小對象色乾,則直接返回
image.jpeg
objc_autoreleasePoolPop 源碼分析
進(jìn)入pop源碼實現(xiàn)君旦,主要由以下幾步
空頁面的處理嘲碱,并根據(jù)token獲取page
容錯處理
通過popPage出棧頁
-
image.jpegimage.jpeg
進(jìn)入releaseUntil實現(xiàn)局蚀,主要是通過循環(huán)遍歷,判斷對象是否等于stop琅绅,其目的是釋放stop之前的所有的對象,
首先通過獲取page的next釋放對象(即page的最后一個對象)宵蛀,并對next進(jìn)行遞減县貌,獲取上一個對象
-
判斷是否是哨兵對象凑懂,如果不是則自動調(diào)用objc_release釋放 image.jpegimage.jpeg
進(jìn)入kill實現(xiàn),主要是銷毀當(dāng)前頁接谨,將當(dāng)前頁賦值為父節(jié)點頁齐媒,并將父節(jié)點頁的child對象指針置為nil
image.jpeg
總結(jié)
在自動釋放池的壓棧(即push)操作中
當(dāng)沒有pool,即只有空占位符(存儲在tls中)時扫夜,則創(chuàng)建頁,壓棧哨兵對象
在頁中壓棧普通對象主要是通過next指針遞增進(jìn)行的笤闯,
-
當(dāng)頁滿了時,需要設(shè)置頁的child對象為新建頁 image.jpeg
在自動釋放池的出棧(即pop)操作中
在頁中出棧普通對象主要是通過next指針遞減進(jìn)行的超陆,
-
當(dāng)頁空了時浦马,需要賦值頁的parent對象為當(dāng)前頁 image.jpeg
相關(guān)面試題
AutoreleasePool 相關(guān)
面試題1:臨時變量什么時候釋放?
- 如果在正常情況下退唠,一般是超出其作用域就會立即釋放
- 如果將臨時變量加入了自動釋放池荤胁,會延遲釋放瞧预,即在runloop休眠或者autoreleasepool作用域之后釋放 面試題2:AutoreleasePool原理
- 自動釋放池的本質(zhì)是一個AutoreleasePoolPage結(jié)構(gòu)體對象,是一個棧結(jié)構(gòu)存儲的頁盆驹,每一個AutoreleasePoolPage都是以雙向鏈表的形式連接
- 自動釋放池的壓棧和出棧主要是通過結(jié)構(gòu)體的構(gòu)造函數(shù)和析構(gòu)函數(shù)調(diào)用底層的objc_autoreleasePoolPush和objc_autoreleasePoolPop滩愁,實際上是調(diào)用AutoreleasePoolPage的push和pop兩個方法
- 每次調(diào)用push操作其實就是創(chuàng)建一個新的AutoreleasePoolPage,而AutoreleasePoolPage的具體操作就是插入一個POOL_BOUNDARY硝枉,并返回插入POOL_BOUNDARY的內(nèi)存地址。而push內(nèi)部調(diào)用autoreleaseFast方法處理妻味,主要有以下三種情況
- 當(dāng)page存在,且不滿時焦履,調(diào)用add方法將對象添加至page的next指針處,并next遞增
- 當(dāng)page存在嘉裤,且已滿時栖博,調(diào)用autoreleaseFullPage初始化一個新的page,然后調(diào)用add方法將對象添加至page棧中
- 當(dāng)page不存在時仇让,調(diào)用autoreleaseNoPage創(chuàng)建一個hotPage,然后調(diào)用add方法將對象添加至page棧中
- 當(dāng)執(zhí)行pop操作時妹孙,會傳入一個值,這個值就是push操作的返回值骇笔,即POOL_BOUNDARY的內(nèi)存地址token嚣崭。所以pop內(nèi)部的實現(xiàn)就是根據(jù)token找到哨兵對象所處的page中笨触,然后使用 objc_release 釋放 token之前的對象雹舀,并把next 指針到正確位置 面試題3:AutoreleasePool能否嵌套使用?
- 可以嵌套使用说榆,其目的是可以控制應(yīng)用程序的內(nèi)存峰值寸认,使其不要太高
- 可以嵌套的原因是因為自動釋放池是以棧為節(jié)點串慰,通過雙向鏈表的形式連接的,且是和線程一一對應(yīng)的
- 自動釋放池的多層嵌套其實就是不停的pushs哨兵對象邦鲫,在pop時,會先釋放里面的古今,在釋放外面的 面試題4:哪些對象可以加入AutoreleasePool滔以?alloc創(chuàng)建可以嗎捉腥?
- 使用new醉者、alloc披诗、copy關(guān)鍵字生成的對象和retain了的對象需要手動釋放,不會被添加到自動釋放池中
- 設(shè)置為autorelease的對象不需要手動釋放剥槐,會直接進(jìn)入自動釋放池
- 所有 autorelease 的對象,在出了作用域之后粒竖,會被自動添加到最近創(chuàng)建的自動釋放池中 面試題5:AutoreleasePool的釋放時機(jī)是什么時候几于?
- App 啟動后蕊苗,蘋果在主線程 RunLoop 里注冊了兩個 Observer,其回調(diào)都是_wrapRunLoopWithAutoreleasePoolHandler()沿彭。
- 第一個 Observer 監(jiān)視的事件是 Entry(即將進(jìn)入 Loop)朽砰,其回調(diào)內(nèi)會調(diào)用 _objc_autoreleasePoolPush() 創(chuàng)建自動釋放池。其 order 是 -2147483647喉刘,優(yōu)先級最高瞧柔,保證創(chuàng) 建釋放池發(fā)生在其他所有回調(diào)之前。
- 第二個 Observer 監(jiān)視了兩個事件: BeforeWaiting(準(zhǔn)備進(jìn)入休眠) 時調(diào)用 _objc_autoreleasePoolPop() 和 _objc_autoreleasePoolPush() 釋放舊的池并創(chuàng)建新池;Exit(即 將退出 Loop) 時調(diào)用 _objc_autoreleasePoolPop() 來釋放自動釋放池睦裳。這個 Observer 的 order 是 2147483647造锅,優(yōu)先級最低,保證其釋放池子發(fā)生在其他所有回調(diào)之后廉邑。 面試題6:thread 和 AutoreleasePool的關(guān)系
- 每個線程哥蔚,包括主線程在內(nèi)都維護(hù)了自己的自動釋放池堆棧結(jié)構(gòu)
- 新的自動釋放池在被創(chuàng)建時倒谷,會被添加到棧頂;當(dāng)自動釋放池銷毀時糙箍,會從棧中移除
- 對于當(dāng)前線程來說,會將自動釋放的對象放入自動釋放池的棧頂倍靡;在線程停止時猴伶,會自動釋放掉與該線程關(guān)聯(lián)的所有自動釋放池
總結(jié):每個線程都有與之關(guān)聯(lián)的自動釋放池堆棧結(jié)構(gòu),新的pool在創(chuàng)建時會被壓棧到棧頂塌西,pool銷毀時他挎,會被出棧,對于當(dāng)前線程來說捡需,釋放對象會被壓棧到棧頂办桨,線程停止時,會自動釋放與之關(guān)聯(lián)的自動釋放池 面試題7:RunLoop 和 AutoreleasePool的關(guān)系 - 主程序的RunLoop在每次事件循環(huán)之前之前站辉,會自動創(chuàng)建一個 autoreleasePool
- 并且會在事件循環(huán)結(jié)束時呢撞,執(zhí)行drain操作,釋放其中的對象