YYCache是一個高性能的緩存工具,本着閱讀優秀的人的代碼就至關於與優秀的人交流的本意,我拜讀了 YYCache 的開源代碼。這篇文章是本身閱讀源碼時的記錄。node
類名 | 職責 |
---|---|
YYCache | 對 YYMemoryCache 和 YYDiskCache 的操做進行了封裝 |
YYMemoryCache | 線程安全的內存緩存,支持手動和自動清除緩存及對象釋放控制 |
_YYLinkedMap | 雙向鏈表,供 YYMemoryCache 使用來支持 LRU (least-recently-used) 淘汰算法 |
_YYLinkedMapNode | _YYLinkedMap 的結點 |
YYDiskCache | 磁盤緩存 |
YYKVStorage | YYDiskCache 的底層實現類,用於管理磁盤緩存 |
YYKVStorageItem | 內置在 YYKVStorage 中,是 YYKVStorage 內部用於封裝某個緩存的類 |
YYCache 提供了最外層的緩存操做方法,而這些方法都是對 YYMemoryCache 及 YYDiskCache 操做的封裝。git
在 YYCache 中對緩存內容進行的操做都是先調用 YYMemoryCache 再調用 YYDiskCache,例如:github
// 是否包含
- (BOOL)containsObjectForKey:(NSString *)key {
return [_memoryCache containsObjectForKey:key] || [_diskCache containsObjectForKey:key];
}
// 根據 key 查找
- (id<NSCoding>)objectForKey:(NSString *)key {
id<NSCoding> object = [_memoryCache objectForKey:key];
if (!object) {
object = [_diskCache objectForKey:key];
if (object) {
[_memoryCache setObject:object forKey:key];
}
}
return object;
}
// 根據 key 設置緩存
- (void)setObject:(id<NSCoding>)object forKey:(NSString *)key {
[_memoryCache setObject:object forKey:key];
[_diskCache setObject:object forKey:key];
}
// 根據 key 刪除某個緩存
- (void)removeObjectForKey:(NSString *)key {
[_memoryCache removeObjectForKey:key];
[_diskCache removeObjectForKey:key];
}
// 刪除全部緩存
- (void)removeAllObjects {
[_memoryCache removeAllObjects];
[_diskCache removeAllObjects];
}
複製代碼
YYMemoryCache 使用了 _YYLinkedMap 及 _ YYLInkedMapNode 實現了 LRU 淘汰算法。算法
LRU(Least recently used,最近最少使用)算法根據數據的歷史訪問記錄來進行淘汰數據,其核心思想是「若是數據最近被訪問過,那麼未來被訪問的概率也更高」。緩存
在 YYMemoryCache 中使用了雙向鏈表結構來處理緩存:安全
知道了 YYMemoryCache 是怎麼處理緩存以後,咱們來看看這個雙向鏈表的具體實現吧。bash
_YYLinkedMap數據結構
/**
定義的雙向鏈表,是非線程安全的
不推薦直接使用
*/
@interface _YYLinkedMap : NSObject {
@package
CFMutableDictionaryRef _dic; // do not set object directly
NSUInteger _totalCost;
NSUInteger _totalCount;
_YYLinkedMapNode *_head; // MRU, do not change it directly
_YYLinkedMapNode *_tail; // LRU, do not change it directly
BOOL _releaseOnMainThread;
BOOL _releaseAsynchronously;
}
// 在鏈表頭部插入一個新的結點
- (void)insertNodeAtHead:(_YYLinkedMapNode *)node;
// 將某個結點移動到鏈表頭部
- (void)bringNodeToHead:(_YYLinkedMapNode *)node;
/// Remove a inner node and update the total cost.
/// Node should already inside the dic.
- (void)removeNode:(_YYLinkedMapNode *)node;
/// Remove tail node if exist.
- (_YYLinkedMapNode *)removeTailNode;
/// Remove all node in background queue.
- (void)removeAll;
@end
複製代碼
_YYLinkedMapNode多線程
@interface _YYLinkedMapNode : NSObject {
@package
__unsafe_unretained _YYLinkedMapNode *_prev; // 前節點
__unsafe_unretained _YYLinkedMapNode *_next; // 後節點
id _key; // 緩存的 key
id _value; // 緩存數據
NSUInteger _cost; // 佔用大小
NSTimeInterval _time; // 最後一次使用時間
}
@end
複製代碼
關於雙向鏈表的操做這裏就不展開了,若是對鏈表的操做不熟悉的,能夠去前面的文章數據結構學習:線性表(鏈式存儲)中回顧一下。app
YYCache 經過三個維度進行緩存的控制:緩存開銷,緩存數量,緩存時間。咱們能夠手動根據這三個維度去清理緩存,YYCache 自身也有自動清理緩存的策略,而且 YYCache 還監聽了 UIApplicationDidReceiveMemoryWarningNotification 及 UIApplicationDidEnterBackgroundNotification 兩個系統通知,在內存報警和進入後臺時根據用戶的配置選擇是否清空全部緩存。
自動清理
在 YYCache 初始化時,就開啓了緩存的自動清理功能以及監聽了進入後臺和內存報警的通知:
- (instancetype)init {
self = super.init;
.....
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_appDidReceiveMemoryWarningNotification) name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_appDidEnterBackgroundNotification) name:UIApplicationDidEnterBackgroundNotification object:nil];
[self _trimRecursively];
return self;
}
複製代碼
自動清理的實現:
- (void)_trimRecursively {
__weak typeof(self) _self = self;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(_autoTrimInterval * NSEC_PER_SEC)), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
__strong typeof(_self) self = _self;
if (!self) return;
[self _trimInBackground];
[self _trimRecursively];
});
}
- (void)_trimInBackground {
dispatch_async(_queue, ^{
[self _trimToCost:self->_costLimit];
[self _trimToCount:self->_countLimit];
[self _trimToAge:self->_ageLimit];
});
}
複製代碼
這裏的自動清理是一個遞歸調用,按照 cost,count,age 的順序清理掉不符合要求的數據。
通知監聽的實現:
- (void)_appDidReceiveMemoryWarningNotification {
if (self.didReceiveMemoryWarningBlock) {
self.didReceiveMemoryWarningBlock(self);
}
if (self.shouldRemoveAllObjectsOnMemoryWarning) {
[self removeAllObjects];
}
}
- (void)_appDidEnterBackgroundNotification {
if (self.didEnterBackgroundBlock) {
self.didEnterBackgroundBlock(self);
}
if (self.shouldRemoveAllObjectsWhenEnteringBackground) {
[self removeAllObjects];
}
}
複製代碼
YYCache 提供了兩個屬性:
// 是否在收到內存警告時刪除所有緩存。默認爲 YES
@property BOOL shouldRemoveAllObjectsOnMemoryWarning;
// 是否在進入後臺時刪除所有緩存。默認爲 YES
@property BOOL shouldRemoveAllObjectsWhenEnteringBackground;
複製代碼
手動清理
其實上面的自動清理中調用的修剪緩存的方法,所有都提供了外部調用的接口:
- (void)trimToCount:(NSUInteger)count;
- (void)trimToCost:(NSUInteger)cost;
- (void)trimToAge:(NSTimeInterval)age;
複製代碼
手動清理的時候,咱們能夠根據本身的需求對緩存進行控制。
- (void)removeObjectForKey:(id)key {
if (!key) return;
pthread_mutex_lock(&_lock);
_YYLinkedMapNode *node = CFDictionaryGetValue(_lru->_dic, (__bridge const void *)(key));
if (node) {
[_lru removeNode:node];
if (_lru->_releaseAsynchronously) {
dispatch_queue_t queue = _lru->_releaseOnMainThread ? dispatch_get_main_queue() : YYMemoryCacheGetReleaseQueue();
dispatch_async(queue, ^{
[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
});
}
}
pthread_mutex_unlock(&_lock);
}
複製代碼
node 在執行這個方法後出了做用域,reference 減一,可是 block 裏面調用 node,使node 被這個 queue Hold 住,reference 加一, 那麼,執行完這個 block 以後,reference count 減一,就達到了再對應線程裏面釋放目的。
在實現雙向鏈表時,徹底可使用 NSDictionary,可是源碼中使用了更加底層的 CFDictionary 來提升緩存的性能。
在源碼中能夠看到大量使用了多線程來將緩存的自動清理和釋放放到子線程中執行,對性能的提高有很大的幫助。
看了這麼優秀的源碼,咱們能夠看到做者設計接口的一些思路,看到做者爲性能提高作的一些努力。功能的東西大概就是這些,每一個人都能說上幾句,可是設計思路以及爲提高性能作的一些細節更是咱們須要學習的東西。