YYCache是用于Objective-C中用于緩存的第三方框架。此文主要用來(lái)講解該框架的實(shí)現(xiàn)細(xì)節(jié),性能分析、設(shè)計(jì)思路ibireme已經(jīng)講得很清楚了,我這邊就不在分析了。首推個(gè)人blog:iipanda.com,同步更新。
文件結(jié)構(gòu)
- YYCache:同時(shí)實(shí)現(xiàn)內(nèi)存緩存和磁盤緩存且是線程安全的
- YYMemoryCache:實(shí)現(xiàn)內(nèi)存緩存,所有的API都是線程安全的,與其他緩存方式比較不同的是內(nèi)部利用LRU淘汰算法(后面會(huì)介紹)來(lái)提高性能
- YYDiskCache:實(shí)現(xiàn)磁盤緩存,所有的API都是線程安全的,內(nèi)部也采用了LRU淘汰算法,主要SQLite和文件存儲(chǔ)兩種方式
- YYKVStorage:實(shí)現(xiàn)磁盤存儲(chǔ),不推薦直接使用該類,該類不是線程安全的
LRU
LRU(Least recently used,最近最少使用)算法,根據(jù)訪問(wèn)的歷史記錄來(lái)對(duì)數(shù)據(jù)進(jìn)行淘汰
<p>
<p>
簡(jiǎn)單的來(lái)說(shuō)3點(diǎn):
- 有新數(shù)據(jù)加入時(shí)添加到鏈表的頭部
- 每當(dāng)緩存命中(即緩存數(shù)據(jù)被訪問(wèn)),則將數(shù)據(jù)移到鏈表頭部
- 當(dāng)鏈表滿的時(shí)候,將鏈表尾部的數(shù)據(jù)丟棄
在YYMemoryCache中使用來(lái)雙向鏈表和NSDictionary實(shí)現(xiàn)了LRU淘汰算法,后面會(huì)介紹
關(guān)于鎖
YYCache 使用到兩種鎖
- OSSpinLock :自旋鎖,上一篇博客也提及到pthread_mutex
- dispatch_semaphore:信號(hào)量,當(dāng)信號(hào)量為1的時(shí)候充當(dāng)鎖來(lái)用
內(nèi)存緩存用的pthread_mutex:由于pthread_mutex相當(dāng)于do while忙等,等待時(shí)會(huì)消耗大量的CPU資源
磁盤緩存使用的dispatch_semaphore:優(yōu)勢(shì)在于等待時(shí)不會(huì)消耗CPU資源
簡(jiǎn)單的科普就到這,現(xiàn)在來(lái)開始源碼的探索
_YYLinkedMap
@interface _YYLinkedMapNode : NSObject {
@package
__unsafe_unretained _YYLinkedMapNode *_prev; // retained by dic
__unsafe_unretained _YYLinkedMapNode *_next; // retained by dic
id _key;
id _value;
NSUInteger _cost;
NSTimeInterval _time;
}
@end
_YYLinkedMapNode:鏈表的節(jié)點(diǎn)
_prev、_next:分別表示指向上一個(gè)節(jié)點(diǎn)、下一個(gè)節(jié)點(diǎn)
_key:緩存的key
_value:緩存對(duì)象
_cost:內(nèi)存消耗
_time:緩存時(shí)間
@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(最近最少使用算法-清除較不常使用數(shù)據(jù)), do not change it directly
BOOL _releaseOnMainThread;
BOOL _releaseAsynchronously;
}
_YYLinkedMap:鏈表
_dic:用來(lái)保存節(jié)點(diǎn)
_totalCost:總緩存開銷
_head、_tail:頭節(jié)點(diǎn)、尾節(jié)點(diǎn)
_releaseOnMainThread:是否在主線程釋放_(tái)YYLinkedMapNode
_releaseAsynchronously:是否異步釋放_(tái)YYLinkedMapNode
雙向鏈表
- 插入節(jié)點(diǎn)到頭部
- 將除兩邊的節(jié)點(diǎn)移到頭部
- 移除除兩邊的節(jié)點(diǎn)
- 移除尾部節(jié)點(diǎn)
- 移除所有節(jié)點(diǎn)
看下移除所有節(jié)點(diǎn)的代碼:
- (void)removeAll {
_totalCost = 0;
_totalCount = 0;
_head = nil;
_tail = nil;
if (CFDictionaryGetCount(_dic) > 0) {
CFMutableDictionaryRef holder = _dic;
_dic = CFDictionaryCreateMutable(CFAllocatorGetDefault(), 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
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);
}
}
}
這邊通過(guò)雙向鏈表來(lái)對(duì)數(shù)據(jù)進(jìn)行操作,和NSDictionary實(shí)現(xiàn)了LRU淘汰算法。時(shí)間復(fù)雜度0(1),5種操作基本上都是對(duì)頭尾節(jié)點(diǎn)和鏈表節(jié)點(diǎn)的上一個(gè)節(jié)點(diǎn)和下一個(gè)節(jié)點(diǎn)進(jìn)行操作。
YYMemoryCache
這邊介紹兩個(gè)主要的操作:添加緩存,查找緩存<p>
- 添加緩存
- (void)setObject:(id)object forKey:(id)key withCost:(NSUInteger)cost {
if (!key) return;
if (!object) {
// 緩存對(duì)象為nil,直接移除
[self removeObjectForKey:key];
return;
}
// 為了保證線程安全,數(shù)據(jù)操作前進(jìn)行加鎖
pthread_mutex_lock(&_lock);
// 查找緩存
_YYLinkedMapNode *node = CFDictionaryGetValue(_lru->_dic, (__bridge const void *)(key));
// 當(dāng)前時(shí)間
NSTimeInterval now = CACurrentMediaTime();
if (node) {
// 緩存對(duì)象已存在,更新數(shù)據(jù),并移到棧頂
_lru->_totalCost -= node->_cost;
_lru->_totalCost += cost;
node->_cost = cost;
node->_time = now;
node->_value = object;
[_lru bringNodeToHead:node];
} else {
// 緩存對(duì)象不存在,添加數(shù)據(jù),并移到棧頂
node = [_YYLinkedMapNode new];
node->_cost = cost;
node->_time = now;
node->_key = key;
node->_value = object;
[_lru insertNodeAtHead:node];
}
// 判斷當(dāng)前的緩存進(jìn)行是否超出了設(shè)定值,若超出則進(jìn)行整理
if (_lru->_totalCost > _costLimit) {
dispatch_async(_queue, ^{
[self trimToCost:_costLimit];
});
}
// 每次添加數(shù)據(jù)僅有一個(gè),數(shù)量上超出時(shí),直接移除尾部那個(gè)object即可
if (_lru->_totalCount > _countLimit) {
_YYLinkedMapNode *node = [_lru removeTailNode];
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
});
}
}
// 操作結(jié)束,解鎖
pthread_mutex_unlock(&_lock);
}
-
異步線程釋放
里面很多都用到類似的方法,將一個(gè)對(duì)象在異步線程中釋放,來(lái)分析下:
- p
1. 首先通過(guò)node來(lái)對(duì)其進(jìn)行持有,以至于不會(huì)在方法調(diào)用結(jié)束的時(shí)候被銷毀
2. 我要要在其他線程中進(jìn)行銷毀,所以將銷毀操作放在block中,block就會(huì)對(duì)其進(jìn)行持有
3. 這邊在block中隨便調(diào)用了個(gè)方法,保證編譯器不會(huì)優(yōu)化掉這個(gè)操作
4. 當(dāng)block結(jié)束后,node沒(méi)有被持有的時(shí)候,就會(huì)在當(dāng)前線程被release掉了
- 添加緩存
// 這邊從memory中取數(shù)據(jù)時(shí),根據(jù)LRU原則,將最新取出的object放到棧頭
- (id)objectForKey:(id)key {
if (!key) return nil;
pthread_mutex_lock(&_lock);
_YYLinkedMapNode *node = CFDictionaryGetValue(_lru->_dic, (__bridge const void *)(key));
if (node) {
node->_time = CACurrentMediaTime();
[_lru bringNodeToHead:node];
}
pthread_mutex_unlock(&_lock);
return node ? node->_value : nil;
}
YYKVStorage
該文件主要以兩種方式來(lái)實(shí)現(xiàn)磁盤存儲(chǔ):SQLite、File,使用兩種方式混合進(jìn)行存儲(chǔ)主要為了提高讀寫效率。寫入數(shù)據(jù)時(shí),SQLite要比文件的方式更快;讀取數(shù)據(jù)的速度主要取決于文件的大小。據(jù)測(cè)試,在iPhone6中,當(dāng)文件大小超過(guò)20kb時(shí),F(xiàn)ile要比SQLite快的多。所以當(dāng)大文件存儲(chǔ)時(shí)建議用File的方式,小文件更適合用SQLite。<p>
下邊分別對(duì)Save、Remove、Get分別進(jìn)行分析
- Save
- (BOOL)saveItemWithKey:(NSString *)key value:(NSData *)value filename:(NSString *)filename extendedData:(NSData *)extendedData {
// 條件不符合
if (key.length == 0 || value.length == 0) return NO;
if (_type == YYKVStorageTypeFile && filename.length == 0) {
return NO;
}
if (filename.length) { // filename存在 SQLite File兩種方式并行
// 用文件進(jìn)行存儲(chǔ)
if (![self _fileWriteWithName:filename data:value]) {
return NO;
}
// 用SQLite進(jìn)行存儲(chǔ)
if (![self _dbSaveWithKey:key value:value fileName:filename extendedData:extendedData]) {
// 當(dāng)使用SQLite方式存儲(chǔ)失敗時(shí),刪除本地文件存儲(chǔ)
[self _fileDeleteWithName:filename];
return NO;
}
return YES;
} else { // filename不存在 SQLite
if (_type != YYKVStorageTypeSQLite) {
// 這邊去到filename后,刪除filename對(duì)應(yīng)的file文件
NSString *filename = [self _dbGetFilenameWithKey:key];
if (filename) {
[self _fileDeleteWithName:filename];
}
}
// SQLite 進(jìn)行存儲(chǔ)
return [self _dbSaveWithKey:key value:value fileName:nil extendedData:extendedData];
}
}
- Remove
- (BOOL)removeItemForKey:(NSString *)key {
if (key.length == 0) return NO;
switch (_type) {
case YYKVStorageTypeSQLite: {
// 刪除SQLite文件
return [self _dbDeleteItemWithKey:key];
} break;
case YYKVStorageTypeFile:
case YYKVStorageTypeMixed: {
// 獲取filename
NSString *filename = [self _dbGetFilenameWithKey:key];
if (filename) {
// 刪除filename對(duì)的file
[self _fileDeleteWithName:filename];
}
// 刪除SQLite文件
return [self _dbDeleteItemWithKey:key];
} break;
default: return NO;
}
}
- Get
- (NSData *)getItemValueForKey:(NSString *)key {
if (key.length == 0) return nil;
NSData *value = nil;
switch (_type) {
case YYKVStorageTypeFile: { //File
NSString *filename = [self _dbGetFilenameWithKey:key];
if (filename) {
// 根據(jù)filename獲取File
value = [self _fileReadWithName:filename];
if (!value) {
// 當(dāng)value不存在,用對(duì)應(yīng)的key刪除SQLite文件
[self _dbDeleteItemWithKey:key];
value = nil;
}
}
} break;
case YYKVStorageTypeSQLite: {
// SQLite 方式獲取
value = [self _dbGetValueWithKey:key];
} break;
case YYKVStorageTypeMixed: {
NSString *filename = [self _dbGetFilenameWithKey:key];
// filename 存在文件獲取,不存在SQLite方式獲取
if (filename) {
value = [self _fileReadWithName:filename];
if (!value) {
[self _dbDeleteItemWithKey:key];
value = nil;
}
} else {
value = [self _dbGetValueWithKey:key];
}
} break;
}
if (value) {
// 更新文件操作時(shí)間
[self _dbUpdateAccessTimeWithKey:key];
}
return value;
}
File方式主要使用的writeToFile進(jìn)行存儲(chǔ),SQLte直接使用的sqlite3來(lái)對(duì)文件進(jìn)行操作,具體數(shù)據(jù)庫(kù)相關(guān)的操作這邊就不在進(jìn)行分析了,感興趣的自己可以閱讀下
YYDiskCache
YYDiskCache是對(duì)YYKVStorage進(jìn)行的一次封裝,是線程安全的,這邊使用的是dispatch_semaphore_signal來(lái)確保線程的安全。另外他結(jié)合LRU算法,根據(jù)文件的大小自動(dòng)選擇存儲(chǔ)方式來(lái)達(dá)到更好的性能。
- (instancetype)initWithPath:(NSString *)path
inlineThreshold:(NSUInteger)threshold {
self = [super init];
if (!self) return nil;
// 獲取緩存的 YYDiskCache
YYDiskCache *globalCache = _YYDiskCacheGetGlobal(path);
if (globalCache) return globalCache;
// 確定存儲(chǔ)的方式
YYKVStorageType type;
if (threshold == 0) {
type = YYKVStorageTypeFile;
} else if (threshold == NSUIntegerMax) {
type = YYKVStorageTypeSQLite;
} else {
type = YYKVStorageTypeMixed;
}
// 初始化 YYKVStorage
YYKVStorage *kv = [[YYKVStorage alloc] initWithPath:path type:type];
if (!kv) return nil;
// 初始化數(shù)據(jù)
_kv = kv;
_path = path;
_lock = dispatch_semaphore_create(1);
_queue = dispatch_queue_create("com.ibireme.cache.disk", DISPATCH_QUEUE_CONCURRENT);
_inlineThreshold = threshold;
_countLimit = NSUIntegerMax;
_costLimit = NSUIntegerMax;
_ageLimit = DBL_MAX;
_freeDiskSpaceLimit = 0;
_autoTrimInterval = 60;
// 遞歸的去整理文件
[self _trimRecursively];
// 對(duì)當(dāng)前對(duì)象進(jìn)行緩存
_YYDiskCacheSetGlobal(self);
// 通知 APP即將被殺死時(shí)
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_appWillBeTerminated) name:UIApplicationWillTerminateNotification object:nil];
return self;
}
其他的一些操作基本上都是對(duì)YYKVStorage的一些封裝,這邊就不一一分析了。