筆者現(xiàn)在還是一個(gè)剛?cè)腴T iOS 開發(fā)的菜鳥邢滑,跟著部門的大佬在學(xué)習(xí)淳蔼。最近大佬讓我去了解一下 iOS 的內(nèi)存管理機(jī)制坷襟,然后寫一篇博文乡范。這篇博文主要都是從我的筆記中挑一些內(nèi)容來寫的配名,是從 ARC 下一直延伸相關(guān)的內(nèi)容,涉及的知識(shí)不是太全面晋辆。
ARC
- ARC 是在編譯期時(shí)幫我們?cè)谶m當(dāng)?shù)牡胤饺ヌ砑庸芾硪糜?jì)數(shù)的代碼渠脉。Clang 編譯器中有一個(gè)叫“靜態(tài)分析器”(static analyzer),這個(gè)靜態(tài)分析器就會(huì)指明程序里引用計(jì)數(shù)出問題的地方瓶佳,然后告訴 ARC , ARC 就會(huì)自動(dòng)添加管理引用計(jì)數(shù)的代碼芋膘。
- 還有一個(gè) ARC 優(yōu)化器,會(huì)幫我們?nèi)コ嘤嗟?
release
,retain
語(yǔ)句
-
ARC下不能使用管理引用計(jì)數(shù)的代碼
retain
release
autorelease
dealloc
直接調(diào)用上面的方法都會(huì)產(chǎn)生編譯錯(cuò)誤,原因是 ARC 在調(diào)用上面的方法時(shí)涩哟,不是通過普通的 Objc 消息派發(fā)機(jī)制索赏,而是直接調(diào)用底層的 C 語(yǔ)言版本。這樣做的原因是管理內(nèi)存的操作很頻繁贴彼,直接調(diào)用底層的函數(shù)可以節(jié)省 cpu 周期潜腻,性能會(huì)提高,而且也防止程序員手動(dòng)添加管理內(nèi)容的代碼而導(dǎo)致引用計(jì)數(shù)出現(xiàn)混亂的情況器仗,就算手動(dòng)添加內(nèi)存管理的代碼融涣,系統(tǒng)也不會(huì)去調(diào)用,它還是直接走底層的 C 語(yǔ)言函數(shù)精钮。
ARC下不用使autorelease
威鹿,我們可以用autoreleasepool{}
來替代,下面延伸的就是autoreleasepool
轨香。
Autoreleasepool
autoreleasepool
的結(jié)構(gòu)是怎么樣的呢忽你?<br />
autoreleasepool
是由N個(gè)(至少一個(gè))autoreleasepoolpage
(它的結(jié)構(gòu)是棧)相互連接的雙向鏈表。
看了幾遍博文臂容,理清了 autoreleasepool 內(nèi)部究竟是如何工作的科雳,我把大概的意思說一下。
@autoreleasepool{
//代碼內(nèi)容脓杉,把捕捉到的對(duì)象都放到 autoreleasePool
}
實(shí)質(zhì)上糟秘,上面的調(diào)用了兩個(gè)方法,然后演變成下面的代碼
void * atautoreleasepool_obj = objc_autoreleasePoolPush();
// 代碼內(nèi)容球散,把捉到的對(duì)象都放到autoreleasePool
objc_autoreleasePoolPop(atautoreleasepool_obj);
因?yàn)?autoreleasePool 是由 autoreleasePoolPage 構(gòu)成的尿赚,所以自動(dòng)釋放池的PUSH 和 POP 都是由 autoreleasePoolPage 去 PUSH 和 POP
objc_autoreleasePoolPagePush
objc_autoreleasePoolPagePop
而 objc_autoreleasePoolPagePush
里面又調(diào)用autoreleaseFast(POOL_SENTINEL)
先解釋一下POOL_SENTINEL
(哨兵),這個(gè)東西可以說是一個(gè)清理標(biāo)志。我們把對(duì)象一個(gè)個(gè)往自動(dòng)釋放池里放凌净,也就是把對(duì)象一個(gè)個(gè)地往autoreleasePoolPage 進(jìn)棧悲龟,最后呢,我們?cè)谧詈筮M(jìn)棧那個(gè)位置+1泻蚊,放入POOL_SENTINEL
躲舌。到了要 POP 的時(shí)候,給棧底到POOL_SENTINEL
之間的對(duì)象 release性雄。
PUSH的具體做法(也就是autoreleaseFast這個(gè)方法要做的事情)
拿到一個(gè)當(dāng)前 autoreleasePoolPage 對(duì)象 page
- 如果
page
存在没卸,而且不是滿的,那就把對(duì)象進(jìn)去 - `page 存在秒旋,但是滿的约计。新建一個(gè) page,設(shè)置好父子指針迁筛,添加對(duì)象
-
page
不存在煤蚌,那就創(chuàng)建一個(gè),添加對(duì)象<br />
POP 的具體做法
- 拿到當(dāng)前的所在的 Page 對(duì)象
- 調(diào)用
releaseUntil
把對(duì)象 release(這時(shí)用到剛剛說的POOL_SENTINEL
) -
kill
掉已經(jīng)被清空的子 Page
autoreleasePool 應(yīng)該在什么時(shí)候去使用
- 寫基于命令行的程序细卧,就是沒有 UI 框架的時(shí)候尉桩。(這種情況我還得找找資料再做解釋)
- 在循環(huán)中創(chuàng)建了大量的臨時(shí)對(duì)象。
for(int i = 0; i < 10000 ;i ++){
autoreleasepool{
//創(chuàng)建一些臨時(shí)對(duì)象去搞事情
}
}
在循環(huán)內(nèi)部加上 autoreleasepool 贪庙,每一次循環(huán)結(jié)束后蜘犁,那些臨時(shí)的對(duì)象就被釋放掉,不用等到全部的循環(huán)都結(jié)束掉止邮,再去一次釋放这橙,這樣就避免了程序的內(nèi)存一直飆升,到最后一下子又降下來的情況导披。
- 在子線程中需要自動(dòng)創(chuàng)建屈扎。
主線程的 autoreleasepool 是系統(tǒng)幫我們創(chuàng)建好的,但是子線程時(shí)沒有創(chuàng)建的撩匕,所以我們要?jiǎng)?chuàng)建鹰晨。runloop 也是一樣,在子線程中止毕,需要我們自己去創(chuàng)建模蜡。
AutoreleasePool 和 RunLoop 的關(guān)系
- 在 RunLoop 創(chuàng)建之前,會(huì) PUSH 一個(gè)自動(dòng)釋放池
- 在 RunLoop 睡眠時(shí)滓技,會(huì) POP 自動(dòng)釋放池(這個(gè)就解釋放在自動(dòng)釋放池的對(duì)象時(shí)什么時(shí)候釋放的,跟 RunLoop 是密切相關(guān)的)棚潦,然后又 PUSH 一個(gè)新的自動(dòng)釋放池( RunLoop 有可能重新被喚醒工作令漂,所以我們像第一步那樣,在 RunLoop 工作前,創(chuàng)建好自動(dòng)釋放池)
- RunLoop 退出時(shí)叠必,POP 掉最后的那個(gè)自動(dòng)釋放池
其實(shí)還有很多關(guān)于 iOS 內(nèi)存管理的知識(shí)荚孵,之后我會(huì)繼續(xù)補(bǔ)充。這個(gè)是筆者的第一篇博文纬朝,如果發(fā)現(xiàn)博文有錯(cuò)誤的地方收叶,請(qǐng)各位讀者指出來,筆者會(huì)慢慢修正共苛。
我也會(huì)繼續(xù)跟著我們部門的大佬學(xué)習(xí)判没,慢慢掌握更多 iOS 的知識(shí),分享更多的博文隅茎。