Cache 的發(fā)音是 [k??]坡疼,但我還是覺得讀成“卡車”比較有愛驾诈。
這次的主角是 NSCache
。
緩存導(dǎo)致的問題
使用內(nèi)存緩存的好處不言而喻,但無盡地消耗內(nèi)存肯定有問題的案铺。占用過多的內(nèi)存以至于沒有更多空閑內(nèi)存供其他應(yīng)用使用蔬芥,頁面置換頻頻發(fā)生對(duì)性能的影響是不小的。
NSCache 作為內(nèi)存緩存
NSCache
和 NSMutableDictionary
非常相似控汉,同樣是存儲(chǔ)鍵值對(duì)但 NSCache
不需要拷貝 key笔诵。文檔說 NSCache
是 “reactive cache”,當(dāng)內(nèi)存充裕時(shí)能緩存數(shù)據(jù)姑子,內(nèi)存不足時(shí)又能自動(dòng)地丟棄一些數(shù)據(jù)乎婿,但就是沒提到丟棄的規(guī)則是什么。
不過 NSCache
的提供兩個(gè)方法去限制緩存:一個(gè)是 -setCountLimit:
街佑,設(shè)置緩存對(duì)象的最大數(shù)量谢翎,根據(jù)我的觀察(存疑),在對(duì)象數(shù)量到達(dá)極限后加入新對(duì)象沐旨,需要被丟棄的對(duì)象按照 LRU(Least Recently Used)規(guī)則被丟棄掉森逮。
另一個(gè) -setTotalCostLimit:
設(shè)置緩存最大的開銷。cache 在 -setObject:forKey:cost:
的時(shí)候磁携,可以為每個(gè)緩存對(duì)象設(shè)置開銷褒侧。當(dāng) cache 中所有對(duì)象的開銷之和達(dá)到閾值時(shí),cache 會(huì)丟棄數(shù)據(jù)讓開銷的總量回落閾值谊迄。開始我以為引入 cost
這個(gè)概念是為了在丟棄緩存的時(shí)候闷供,優(yōu)先把開銷大的數(shù)據(jù)扔掉,在 cache 中盡量留著盡量多的數(shù)據(jù)统诺,結(jié)果還是 LRU歪脏。
簡(jiǎn)言之,項(xiàng)目中用 NSMutableDictionary
對(duì)象做緩存的都換為 NSCache
會(huì)比較好好粮呢,畢竟能自動(dòng)丟棄唾糯。
使用 Purgeable 內(nèi)存
當(dāng)應(yīng)用申請(qǐng)一塊內(nèi)存時(shí),倘若系統(tǒng)發(fā)現(xiàn)沒用更多的空閑內(nèi)存鬼贱,那么需要將已用內(nèi)存中的數(shù)據(jù)置換到磁盤上移怯,這樣就有了內(nèi)存可供使用。但如果該內(nèi)存被標(biāo)記為 purgeable(可清除的)这难,那么內(nèi)存中的數(shù)據(jù)就會(huì)直接被丟棄舟误。而這個(gè)機(jī)制是對(duì)于那些 “創(chuàng)建開銷” 小于 "頁面置換開銷" 的對(duì)象是有益的。
類似于線程中的同步機(jī)制姻乓,purgeable 的內(nèi)存訪問也需要一套 locking mechanism嵌溢,在這樣的一個(gè)機(jī)制中有 counter 這樣的概念眯牧,指示著對(duì)象的生命周期:
- 當(dāng) counter >= 1, 對(duì)象可以被使用赖草;
- 當(dāng) counter == 0学少,對(duì)象的就可以丟棄。
NSPurgebleData
是一個(gè)遵循 NSDiscardabelContent
協(xié)議的類秧骑,作為使用 Purgeable 內(nèi)存的實(shí)現(xiàn)版确,通過 begin/endContentAccess 控制 counter,需要注意以下幾點(diǎn):
-
-beginContentAccess
讓 counter++乎折; -
-endContentAccess
讓 counter--绒疗; - 當(dāng) counter >= 1 時(shí)才能訪問對(duì)象占用的內(nèi)存;
- 只有在對(duì)象的 counter == 0 時(shí)骂澄,發(fā)送
-discardContentIfPossible
消息才釋放內(nèi)存吓蘑,否則啥也不做。
下面的代碼表示了 NSPurgeableData 的簡(jiǎn)單使用坟冲,抄自 WWDC 2013, Session 704磨镶。
// 初始化完成 counter == 1
NSPurgeableData * purgeabelData = [[NSPurgeableData alloc] initWithBytes:fileBytes length:fileLen];
// 不需要使用數(shù)據(jù)時(shí)發(fā)送 endContentAccess 消息,counter == 0
[purgeabelData endContentAccess];
// 一段時(shí)間之后
// 發(fā)送 beginContentAccess 以獲取數(shù)據(jù)健提,
// 如果返回 NO棋嘲,則說明在內(nèi)存緊張時(shí),
// 這個(gè)對(duì)象指向的內(nèi)存釋放矩桂,需要重新生成沸移。
// counter == 1
if ([purgeabelData beginContentAccess] == NO) {
purgeabelData = [[NSPurgeableData alloc] initWithBytes:fileBytes length:fileLen];
}
// 結(jié)束對(duì)對(duì)象的存取, counter == 0
[purgeabelData endContentAccess];
混合使用
NSPurgeableData
不是必須和 NSCache
搭配使用,不過前面的 WWDC 的 session 和文檔中都有提到這一用法侄榴。NSCache
緩存可丟棄對(duì)象雹锣,如 NSPurgeableData
,會(huì)形成 NSCache --> NSPurgeableData --> Purgeable Memory Region
這樣的關(guān)系鏈癞蚕,當(dāng) NSPurgeableData
指向的內(nèi)存中數(shù)據(jù)被丟棄時(shí)候蕊爵,cache 中的 NSPurgeableData
也會(huì)被移除。