YYMemoryCache學(xué)習(xí)

YYCache tips

之前YYKit剛開源的時(shí)候就粗略讀過源碼,當(dāng)時(shí)真的是震驚,最近工作不忙,想細(xì)細(xì)讀一遍,每次讀作者的源碼,膝蓋都沒有直起來過 - -~.

簡介

  • 簡單說一下YYCache的幾個(gè)比較好的特性吧

    • LRU: 緩存支持 LRU (least-recently-used) 淘汰算法删窒。
    • 緩存控制: 支持多種緩存控制方法:總數(shù)量甜紫、總大小焕刮、存活時(shí)間可缚、空閑空間振湾。
    • 兼容性: API 基本和 NSCache 保持一致, 所有方法都是線程安全的。
    • 內(nèi)存緩存對象釋放控制: 對象的釋放(release) 可以配置為同步或異步進(jìn)行婶恼,可以配置在主線程或后臺線程進(jìn)行楞卡。
    • 自動清空: 當(dāng)收到內(nèi)存警告或 App 進(jìn)入后臺時(shí),緩存可以配置為自動清空宛琅。
    • 磁盤緩存可定制性: 磁盤緩存支持自定義的歸檔解檔方法刻蟹,以支持那些沒有實(shí)現(xiàn) NSCoding 協(xié)議的對象。
    • 存儲類型控制: 磁盤緩存支持對每個(gè)對象的存儲類型 (SQLite/文件) 進(jìn)行自動或手動控制嘿辟,以獲得更高的存取性能
  • github中看詳細(xì)的介紹和使用方法,本文主要寫自己在學(xué)習(xí)過程中的收獲總結(jié)

YYMemoryCache

  • YYMemoryCache類的大體結(jié)構(gòu)
YYMemoryCache

  • iOS中為了防止多線程對資源的搶奪,所有開發(fā)時(shí)使用鎖來保證線程的安全,在這篇文章中作者詳細(xì)介紹了iOS中幾種鎖的性能對比以及安全性的討論
iOS中的鎖

在YYCache中采用的是pthread_mutex.

//創(chuàng)建一個(gè)`pthread_mutex`鎖
pthread_mutex_init(&_lock, NULL);
//在鎖中操作對象
pthread_mutex_lock(&_lock);
// do something safely...
pthread_mutex_unlock(&_lock)

LRU淘汰算法

  • 當(dāng)LRU的實(shí)現(xiàn):It uses LRU (least-recently-used) to remove objects; NSCache's eviction method,在YYMemeryCache中使用了lru規(guī)則來進(jìn)行緩存的淘汰,當(dāng)發(fā)生內(nèi)存警告或者緩存值達(dá)到上限,會優(yōu)先淘汰哪些時(shí)間戳靠前的對象,最近使用的不被淘汰.YYMemoryCache中兩個(gè)重要的內(nèi)部對象_YYLinkedMap,_YYLinkedMapNode
_YYLinkedMapNode
  • _YYLinkedMapNode 是緩存系統(tǒng)中的最小單元,是對被存儲對象的一層包裝,直接被_YYLinkedMap所持有,先來看看這個(gè)類的聲明
@interface _YYLinkedMapNode : NSObject {
@package
__unsafe_unretained _YYLinkedMapNode *_prev; // retained by dic
__unsafe_unretained _YYLinkedMapNode *_next; // retained by dic
id _key; //鎖存對象的key
id _value; //具體存儲的對象
NSUInteger _cost; // 所存對象占用空間
NSTimeInterval _time; // 最近一次使用該對象的時(shí)間戳
}

也就是這個(gè)對象中擁有了一個(gè)被存儲對象全部的信息:key,元對象,以及在linkMap中的location,location的實(shí)現(xiàn)是通過持有前一個(gè)對象的指針以及后一個(gè)對象的指針來實(shí)現(xiàn)的

_YYLinkedMap
  • _YYLinkedMap是實(shí)現(xiàn)lru的關(guān)鍵,它是(_YYLinkedMapNode *)的集合,通過記錄集合內(nèi)每個(gè)node對象的前后關(guān)系實(shí)現(xiàn)一個(gè)堆棧,本質(zhì)是使用了CFMutableDictionaryRef來進(jìn)行對象的存儲,這個(gè)集合管理了對象的出棧,入棧以及排序,相關(guān)的方法依次有
// 將一個(gè)node對象插到隊(duì)列最前面
- (void)insertNodeAtHead:(_YYLinkedMapNode *)node;

// 將一個(gè)node放到隊(duì)列最前面
- (void)bringNodeToHead:(_YYLinkedMapNode *)node;

//移除掉指定node
- (void)removeNode:(_YYLinkedMapNode *)node;

//將最后一個(gè)個(gè)node移除
- (_YYLinkedMapNode *)removeTailNode;

//清除隊(duì)列
- (void)removeAll
  • 以上是YYMemoryCache中兩個(gè)重要的類,在每次給memoryCache發(fā)送setObject:forkey:或者objectForKey:消息的時(shí)候都會更新對應(yīng)的linkedMapNode對象的時(shí)間戳屬性,并且把該對象放到隊(duì)列的最前面,從而調(diào)整了緩存中對象的順序.

內(nèi)存緩存對象釋放控制

if (_releaseAsynchronously) {
dispatch_queue_t queue = _releaseOnMainThread ? dispatch_get_main_queue() : YYMemoryCacheGetReleaseQueue();
dispatch_async(queue, ^{
CFRelease(holder); // hold and release in specified queue
});
} else if (_releaseOnMainThread && !pthread_main_np()) {
dispatch_async(dispatch_get_main_queue(), ^{
CFRelease(holder); // hold and release in specified queue
});
} else {
CFRelease(holder);
}
  • 我們知道對象的創(chuàng)建需要分配內(nèi)存空間,大量的創(chuàng)建對象會比較消耗性能,同樣大量的對象的釋放操作也是比較消耗性能的,所以在YYMemeryCache中提供了可以異步,并且選擇子線程進(jìn)行對象的釋放的選項(xiàng),這里釋放操作比較巧妙我不是很理解,記錄一下. 我暫時(shí)的理解是利用了block能夠捕獲外部變量,導(dǎo)致當(dāng)執(zhí)行到dispatch_async(queue, ^{雖然node已經(jīng)被置為nil了,但是node對象并不會被馬上釋放(被block所捕獲),等到切換到相應(yīng)線程中以后對這個(gè)node對象發(fā)消息,編譯器發(fā)現(xiàn)這個(gè)node已經(jīng)被置空了, 才會馬上釋放該對象.
if (_lru->_totalCount > _countLimit) {
_YYLinkedMapNode *node = [_lru removeTailNode];
if (_lru->_releaseAsynchronously) {
dispatch_queue_t queue = _lru->_releaseOnMainThread ? dispatch_get_main_queue() : YYMemoryCacheGetReleaseQueue();
//node并不會馬上釋放,因?yàn)楸籦lock捕獲了
dispatch_async(queue, ^{
//在這里可以實(shí)現(xiàn)在異步線程中釋放對象?
[node class]; //hold and release in queue
});
} else if (_lru->_releaseOnMainThread && !pthread_main_np()) {
dispatch_async(dispatch_get_main_queue(), ^{
[node class]; //hold and release in queue
});
}
}

緩存上限的控制

  • 在內(nèi)存緩存中作者采用了輪詢的方式來控制內(nèi)存緩存中緩存上限,緩存?zhèn)€數(shù)以及過期時(shí)間,默認(rèn)輪詢時(shí)間是5秒,并且次輪訓(xùn)操作放到異步線程中,采用低優(yōu)先級以獲取較高的性能
  • 作者定義了三個(gè)方法- _trimToCost:,-_trimToCount:,-_trimToAge:來分別限制最大緩存字節(jié)數(shù),對象個(gè)數(shù),緩存時(shí)間,我們拿其中一個(gè)來看其中的知識點(diǎn)
- (void)_trimToCost:(NSUInteger)costLimit {
BOOL finish = NO;
pthread_mutex_lock(&_lock);
if (costLimit == 0) {
[_lru removeAll];
finish = YES;
} else if (_lru->_totalCost <= costLimit) {
finish = YES;
}
pthread_mutex_unlock(&_lock);
if (finish) return;

NSMutableArray *holder = [NSMutableArray new];
while (!finish) {

//pthread_mutex_trylock函數(shù)是pthread_mutex_lock函數(shù)的非阻塞版本,也可以用來加鎖
與pthread_mutex_lock的區(qū)別是:trylock如果沒有獲取到鎖就會立刻返回不會阻塞當(dāng)前線程,獲取鎖成功會返回0,否則返回其他值來說明鎖的狀態(tài).
但是lock如果沒有獲取到鎖會一直等待從而發(fā)生阻塞.

//獲取鎖成功后加鎖
if (pthread_mutex_trylock(&_lock) == 0) {
if (_lru->_totalCost > costLimit) {
_YYLinkedMapNode *node = [_lru removeTailNode];
if (node) [holder addObject:node];
} else {
finish = YES;
}
pthread_mutex_unlock(&_lock);
} else {
//獲取鎖失敗將當(dāng)前線程掛起10ms
usleep(10 * 1000); //10 ms
}
}
//這里holder雖然是臨時(shí)變量,超過函數(shù){}范圍后以后會被釋放掉.
//這里同樣是利用了block的捕獲變量能力來達(dá)到后臺線程釋放對象.
if (holder.count) {
dispatch_queue_t queue = _lru->_releaseOnMainThread ? dispatch_get_main_queue() : YYMemoryCacheGetReleaseQueue();
dispatch_async(queue, ^{
[holder count]; // release in queue
});
}
}
  • 以上就是YYMemoryCache中的關(guān)鍵技術(shù)點(diǎn)和實(shí)現(xiàn)思路,從中學(xué)習(xí)到很多有用的知識,例如對象的釋放選擇性的放到子線程中,iru淘汰算法的實(shí)現(xiàn),類之間的設(shè)計(jì)思路以及作者嚴(yán)謹(jǐn)?shù)拇a風(fēng)格.以后還會分析YYDiskCache的具體實(shí)現(xiàn).

相關(guān)鏈接:

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末舆瘪,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子红伦,更是在濱河造成了極大的恐慌英古,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,185評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件昙读,死亡現(xiàn)場離奇詭異哺呜,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)箕戳,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,652評論 3 393
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來国撵,“玉大人陵吸,你說我怎么就攤上這事〗檠溃” “怎么了壮虫?”我有些...
    開封第一講書人閱讀 163,524評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長环础。 經(jīng)常有香客問我囚似,道長,這世上最難降的妖魔是什么线得? 我笑而不...
    開封第一講書人閱讀 58,339評論 1 293
  • 正文 為了忘掉前任饶唤,我火速辦了婚禮,結(jié)果婚禮上贯钩,老公的妹妹穿的比我還像新娘募狂。我一直安慰自己办素,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,387評論 6 391
  • 文/花漫 我一把揭開白布祸穷。 她就那樣靜靜地躺著性穿,像睡著了一般。 火紅的嫁衣襯著肌膚如雪雷滚。 梳的紋絲不亂的頭發(fā)上需曾,一...
    開封第一講書人閱讀 51,287評論 1 301
  • 那天,我揣著相機(jī)與錄音祈远,去河邊找鬼呆万。 笑死,一個(gè)胖子當(dāng)著我的面吹牛绊含,可吹牛的內(nèi)容都是我干的桑嘶。 我是一名探鬼主播,決...
    沈念sama閱讀 40,130評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼躬充,長吁一口氣:“原來是場噩夢啊……” “哼逃顶!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起充甚,我...
    開封第一講書人閱讀 38,985評論 0 275
  • 序言:老撾萬榮一對情侶失蹤以政,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后伴找,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體慰枕,經(jīng)...
    沈念sama閱讀 45,420評論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡友瘤,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,617評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片病游。...
    茶點(diǎn)故事閱讀 39,779評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖花鹅,靈堂內(nèi)的尸體忽然破棺而出疙赠,到底是詐尸還是另有隱情,我是刑警寧澤樊零,帶...
    沈念sama閱讀 35,477評論 5 345
  • 正文 年R本政府宣布我磁,位于F島的核電站,受9級特大地震影響驻襟,放射性物質(zhì)發(fā)生泄漏夺艰。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,088評論 3 328
  • 文/蒙蒙 一沉衣、第九天 我趴在偏房一處隱蔽的房頂上張望郁副。 院中可真熱鬧,春花似錦厢蒜、人聲如沸霞势。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,716評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽愕贡。三九已至草雕,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間固以,已是汗流浹背墩虹。 一陣腳步聲響...
    開封第一講書人閱讀 32,857評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留憨琳,地道東北人诫钓。 一個(gè)月前我還...
    沈念sama閱讀 47,876評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像篙螟,于是被迫代替她去往敵國和親菌湃。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,700評論 2 354

推薦閱讀更多精彩內(nèi)容

  • YYCache簡介 YYCache由YYMemoryCache(高速內(nèi)存緩存)和YYDiskCache(低速磁盤緩...
    簡書lu閱讀 1,451評論 0 5
  • 概述 上一篇主要講解了YYCache的文件結(jié)構(gòu)遍略,分析了YYCache類的相關(guān)方法惧所,本章主要分析內(nèi)存緩存類YYMem...
    egoCogito_panf閱讀 3,158評論 2 12
  • YYCache是用于Objective-C中用于緩存的第三方框架。此文主要用來講解該框架的實(shí)現(xiàn)細(xì)節(jié)绪杏,性能分析下愈、設(shè)計(jì)...
    JonesCxy閱讀 561評論 0 2
  • 第三章 Java內(nèi)存模型 3.1 Java內(nèi)存模型的基礎(chǔ) 通信在共享內(nèi)存的模型里,通過寫-讀內(nèi)存中的公共狀態(tài)進(jìn)行隱...
    澤毛閱讀 4,352評論 2 22
  • 不尊重合約,沒有契約精神僧著,這種玩法真是心累履因。 法國公司以財(cái)務(wù)管控,投資管控為首盹愚,可是不尊重合同契約搓逾,這也是個(gè)不想有...
    娟姐的心語話廊閱讀 580評論 0 1