筆者現(xiàn)在還是一個(gè)剛?cè)腴T(mén) iOS 開(kāi)發(fā)的菜鳥(niǎo),跟著部門(mén)的大佬在學(xué)習(xí)。最近大佬讓我去了解一下 iOS 的內(nèi)存管理機(jī)制,然后寫(xiě)一篇博文。這篇博文主要都是從我的筆記中挑一些內(nèi)容來(lái)寫(xiě)的,是從 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ù)出問(wèn)題的地方,然后告訴 ARC , ARC 就會(huì)自動(dòng)添加管理引用計(jì)數(shù)的代碼。
- 還有一個(gè) ARC 優(yōu)化器,會(huì)幫我們?nèi)コ嘤嗟?
release,retain語(yǔ)句
-
ARC下不能使用管理引用計(jì)數(shù)的代碼
retainreleaseautoreleasedealloc
直接調(diào)用上面的方法都會(huì)產(chǎn)生編譯錯(cuò)誤,原因是 ARC 在調(diào)用上面的方法時(shí),不是通過(guò)普通的 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{}來(lái)替代,下面延伸的就是autoreleasepool。
Autoreleasepool
autoreleasepool的結(jié)構(gòu)是怎么樣的呢?<br />
autoreleasepool是由N個(gè)(至少一個(gè))autoreleasepoolpage(它的結(jié)構(gòu)是棧)相互連接的雙向鏈表。

看了幾遍博文,理清了 autoreleasepool 內(nèi)部究竟是如何工作的,我把大概的意思說(shuō)一下。
@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è)東西可以說(shuō)是一個(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存在,而且不是滿(mǎn)的,那就把對(duì)象進(jìn)去 - `page 存在,但是滿(mǎn)的。新建一個(gè) page,設(shè)置好父子指針,添加對(duì)象
-
page不存在,那就創(chuàng)建一個(gè),添加對(duì)象<br />
POP 的具體做法
- 拿到當(dāng)前的所在的 Page 對(duì)象
- 調(diào)用
releaseUntil把對(duì)象 release(這時(shí)用到剛剛說(shuō)的POOL_SENTINEL) -
kill掉已經(jīng)被清空的子 Page
autoreleasePool 應(yīng)該在什么時(shí)候去使用
- 寫(xiě)基于命令行的程序,就是沒(méi)有 UI 框架的時(shí)候。(這種情況我還得找找資料再做解釋?zhuān)?/li>
- 在循環(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)存一直飆升,到最后一下子又降下來(lái)的情況。
- 在子線程中需要自動(dòng)創(chuàng)建。
主線程的 autoreleasepool 是系統(tǒng)幫我們創(chuàng)建好的,但是子線程時(shí)沒(méi)有創(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)各位讀者指出來(lái),筆者會(huì)慢慢修正。
我也會(huì)繼續(xù)跟著我們部門(mén)的大佬學(xué)習(xí),慢慢掌握更多 iOS 的知識(shí),分享更多的博文。