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)鏈接: