
本文授權轉載,作者:漢斯哈哈哈(公眾號:hans_iOS)
導讀:
YYCache源碼分析(一)
YYCache源碼分析(二)
正文:
本文分析YYDiskCache->YYKVStorage實現過程:
YYDiskCache對YYKVStorage一層封裝,緩存方式:數據庫+文件,下面先分析主要實現類YYKVStorage,再分析表層類YYDiskCache。

YYKVStorage.h方法結構圖

YYKVStorage.h方法解釋
#importNS_ASSUME_NONNULL_BEGIN // 用YYKVStorageItem保存緩存相關參數 @interface YYKVStorageItem : NSObject // 緩存鍵值 @property (nonatomic, strong) NSString *key; // 緩存對象 @property (nonatomic, strong) NSData *value; // 緩存文件名 @property (nullable, nonatomic, strong) NSString *filename; // 緩存大小 @property (nonatomic) int size; // 修改時間 @property (nonatomic) int modTime; // 最後使用時間 @property (nonatomic) int accessTime; // 拓展數據 @property (nullable, nonatomic, strong) NSData *extendedData; @end // 可以指定緩存類型 typedef NS_ENUM(NSUInteger, YYKVStorageType) { // 文件緩存(filename != null) YYKVStorageTypeFile = 0, // 數據庫緩存 YYKVStorageTypeSQLite = 1, // 如果filename != null,則value用文件緩存,緩存的其他參數用數據庫緩存;如果filename == null,則用數據庫緩存 YYKVStorageTypeMixed = 2, }; // 緩存操作實現 @interface YYKVStorage : NSObject #pragma mark - Attribute // 緩存路徑 @property (nonatomic, readonly) NSString *path; // 緩存方式 @property (nonatomic, readonly) YYKVStorageType type; // 是否要打開錯誤日志 @property (nonatomic) BOOL errorLogsEnabled; #pragma mark - Initializer // 這兩個方法不能使用,因為實例化對象時要有初始化path、type - (instancetype)init UNAVAILABLE_ATTRIBUTE; + (instancetype)new UNAVAILABLE_ATTRIBUTE; /** * 實例化對象 * * @param path 緩存路徑 * @param type 緩存方式 */ - (nullable instancetype)initWithPath:(NSString *)path type:(YYKVStorageType)type NS_DESIGNATED_INITIALIZER; #pragma mark - Save Items /** * 添加緩存 * * @param item 把緩存數據封裝到YYKVStorageItem對象 */ - (BOOL)saveItem:(YYKVStorageItem *)item; /** * 添加緩存 * * @param key 緩存鍵值 * @param value 緩存對象 */ - (BOOL)saveItemWithKey:(NSString *)key value:(NSData *)value; /** * 添加緩存 * * @param key 緩存鍵值 * @param value 緩存對象 * @param filename 緩存文件名稱 * filename != null * 則用文件緩存value,並把`key`,`filename`,`extendedData`寫入數據庫 * filename == null * 緩存方式type:YYKVStorageTypeFile 不進行緩存 * 緩存方式type:YYKVStorageTypeSQLite || YYKVStorageTypeMixed 數據庫緩存 * @param extendedData 緩存拓展數據 */ - (BOOL)saveItemWithKey:(NSString *)key value:(NSData *)value filename:(nullable NSString *)filename extendedData:(nullable NSData *)extendedData; #pragma mark - Remove Items /** * 刪除緩存 */ - (BOOL)removeItemForKey:(NSString *)key; - (BOOL)removeItemForKeys:(NSArray *)keys; /** * 刪除所有內存開銷大於size的緩存 */ - (BOOL)removeItemsLargerThanSize:(int)size; /** * 刪除所有時間比time小的緩存 */ - (BOOL)removeItemsEarlierThanTime:(int)time; /** * 減小緩存占的容量開銷,使總緩存的容量開銷值不大於maxSize(刪除原則:LRU 最久未使用的緩存將先刪除) */ - (BOOL)removeItemsToFitSize:(int)maxSize; /** * 減小總緩存數量,使總緩存數量不大於maxCount(刪除原則:LRU 最久未使用的緩存將先刪除) */ - (BOOL)removeItemsToFitCount:(int)maxCount; /** * 清空所有緩存 */ - (BOOL)removeAllItems; - (void)removeAllItemsWithProgressBlock:(nullable void(^)(int removedCount, int totalCount))progress endBlock:(nullable void(^)(BOOL error))end; #pragma mark - Get Items /** * 讀取緩存 */ - (nullable YYKVStorageItem *)getItemForKey:(NSString *)key; - (nullable YYKVStorageItem *)getItemInfoForKey:(NSString *)key; - (nullable NSData *)getItemValueForKey:(NSString *)key; - (nullable NSArray *)getItemForKeys:(NSArray *)keys; - (nullable NSArray *)getItemInfoForKeys:(NSArray *)keys; - (nullable NSDictionary *)getItemValueForKeys:(NSArray *)keys; #pragma mark - Get Storage Status /** * 判斷當前key是否有對應的緩存 */ - (BOOL)itemExistsForKey:(NSString *)key; /** * 獲取緩存總數量 */ - (int)getItemsCount; /** * 獲取緩存總內存開銷 */ - (int)getItemsSize; @end NS_ASSUME_NONNULL_END
YYKVStorage.m方法結構圖
私有方法:

公開方法:

YYKVStorage.m方法實現
下面分別拎出添加、刪除、查找各個主要的方法來講解
1.添加
// 添加緩存
- (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) {
//** `緩存方式為YYKVStorageTypeFile(文件緩存)`並且`未傳緩存文件名`則不緩存(忽略) **
return NO;
}
if (filename.length) {
//** 存在文件名則用文件緩存,並把`key`,`filename`,`extendedData`寫入數據庫 **
// 緩存數據寫入文件
if (![self _fileWriteWithName:filename data:value]) {
return NO;
}
// 把`key`,`filename`,`extendedData`寫入數據庫,存在filenam,則不把value緩存進數據庫
if (![self _dbSaveWithKey:key value:value fileName:filename extendedData:extendedData]) {
// 如果數據庫操作失敗,刪除之前的文件緩存
[self _fileDeleteWithName:filename];
return NO;
}
return YES;
} else {
if (_type != YYKVStorageTypeSQLite) {
// ** 緩存方式:非數據庫 **
// 根據緩存key查找緩存文件名
NSString *filename = [self _dbGetFilenameWithKey:key];
if (filename) {
// 刪除文件緩存
[self _fileDeleteWithName:filename];
}
}
// 把緩存寫入數據庫
return [self _dbSaveWithKey:key value:value fileName:nil extendedData:extendedData];
}
}
// 把data寫入文件
- (BOOL)_fileWriteWithName:(NSString *)filename data:(NSData *)data {
// 拼接文件路徑
NSString *path = [_dataPath stringByAppendingPathComponent:filename];
// 寫入文件
return [data writeToFile:path atomically:NO];
}
// 寫入數據庫
- (BOOL)_dbSaveWithKey:(NSString *)key value:(NSData *)value fileName:(NSString *)fileName extendedData:(NSData *)extendedData {
// 執行sql語句
NSString *sql = @"insert or replace into manifest (key, filename, size, inline_data, modification_time, last_access_time, extended_data) values (?1, ?2, ?3, ?4, ?5, ?6, ?7);";
// 所有sql執行前,都必須能run
sqlite3_stmt *stmt = [self _dbPrepareStmt:sql];
if (!stmt) return NO;
// 時間
int timestamp = (int)time(NULL);
// 綁定參數值
sqlite3_bind_text(stmt, 1, key.UTF8String, -1, NULL);
sqlite3_bind_text(stmt, 2, fileName.UTF8String, -1, NULL);
sqlite3_bind_int(stmt, 3, (int)value.length);
if (fileName.length == 0) {
// fileName為null時,緩存value
sqlite3_bind_blob(stmt, 4, value.bytes, (int)value.length, 0);
} else {
// fileName不為null時,不緩存value
sqlite3_bind_blob(stmt, 4, NULL, 0, 0);
}
sqlite3_bind_int(stmt, 5, timestamp);
sqlite3_bind_int(stmt, 6, timestamp);
sqlite3_bind_blob(stmt, 7, extendedData.bytes, (int)extendedData.length, 0);
// 執行操作
int result = sqlite3_step(stmt);
if (result != SQLITE_DONE) {
//** 未完成執行數據庫 **
// 輸出錯誤logs
if (_errorLogsEnabled) NSLog(@"%s line:%d sqlite insert error (%d): %s", __FUNCTION__, __LINE__, result, sqlite3_errmsg(_db));
return NO;
}
return YES;
}
// 將sql編譯成stmt
- (sqlite3_stmt *)_dbPrepareStmt:(NSString *)sql {
if (![self _dbCheck] || sql.length == 0 || !_dbStmtCache) return NULL;
// 從_dbStmtCache字典裡取之前編譯過sql的stmt(優化)
sqlite3_stmt *stmt = (sqlite3_stmt *)CFDictionaryGetValue(_dbStmtCache, (__bridge const void *)(sql));
if (!stmt) {
// 將sql編譯成stmt
int result = sqlite3_prepare_v2(_db, sql.UTF8String, -1, &stmt, NULL);
if (result != SQLITE_OK) {
//** 未完成執行數據庫 **
// 輸出錯誤logs
if (_errorLogsEnabled) NSLog(@"%s line:%d sqlite stmt prepare error (%d): %s", __FUNCTION__, __LINE__, result, sqlite3_errmsg(_db));
return NULL;
}
// 將新的stmt緩存到字典
CFDictionarySetValue(_dbStmtCache, (__bridge const void *)(sql), stmt);
} else {
// 重置stmt狀態
sqlite3_reset(stmt);
}
return stmt;
}
// 刪除文件
- (BOOL)_fileDeleteWithName:(NSString *)filename {
NSString *path = [_dataPath stringByAppendingPathComponent:filename];
return [[NSFileManager defaultManager] removeItemAtPath:path error:NULL];
}
// 從數據庫查找文件名
- (NSString *)_dbGetFilenameWithKey:(NSString *)key {
// 准備執行sql
NSString *sql = @"select filename from manifest where key = ?1;";
sqlite3_stmt *stmt = [self _dbPrepareStmt:sql];
if (!stmt) return nil;
// 綁定參數
sqlite3_bind_text(stmt, 1, key.UTF8String, -1, NULL);
// 執行操作
int result = sqlite3_step(stmt);
if (result == SQLITE_ROW) {
//** 存在可讀的row **
// 取出stmt中的數據
char *filename = (char *)sqlite3_column_text(stmt, 0);
if (filename && *filename != 0) {
// 轉utf8 string
return [NSString stringWithUTF8String:filename];
}
} else {
if (result != SQLITE_DONE) {
//** 未完成執行數據庫 **
// 輸出錯誤logs
if (_errorLogsEnabled) NSLog(@"%s line:%d sqlite query error (%d): %s", __FUNCTION__, __LINE__, result, sqlite3_errmsg(_db));
}
}
return nil;
}2.刪除
// 刪除緩存
- (BOOL)removeItemForKey:(NSString *)key {
if (key.length == 0) return NO;
// 判斷緩存方式
switch (_type) {
case YYKVStorageTypeSQLite: {
//** 數據庫緩存 **
// 刪除數據庫記錄
return [self _dbDeleteItemWithKey:key];
} break;
case YYKVStorageTypeFile:
case YYKVStorageTypeMixed: {
//** 數據庫緩存 或 文件緩存 **
// 查找緩存文件名
NSString *filename = [self _dbGetFilenameWithKey:key];
if (filename) {
// 刪除文件緩存
[self _fileDeleteWithName:filename];
}
// 刪除數據庫記錄
return [self _dbDeleteItemWithKey:key];
} break;
default: return NO;
}
}
// 刪除數據庫記錄
- (BOOL)_dbDeleteItemWithKey:(NSString *)key {
// 准備執行sql
NSString *sql = @"delete from manifest where key = ?1;";
sqlite3_stmt *stmt = [self _dbPrepareStmt:sql];
if (!stmt) return NO;
// 綁定參數
sqlite3_bind_text(stmt, 1, key.UTF8String, -1, NULL);
// 執行操作
int result = sqlite3_step(stmt);
if (result != SQLITE_DONE) {
//** 未完成執行數據庫 **
// 輸出錯誤logs
if (_errorLogsEnabled) NSLog(@"%s line:%d db delete error (%d): %s", __FUNCTION__, __LINE__, result, sqlite3_errmsg(_db));
return NO;
}
return YES;
}3.查找
// 查找緩存
- (YYKVStorageItem *)getItemForKey:(NSString *)key {
if (key.length == 0) return nil;
// 數據庫查詢
YYKVStorageItem *item = [self _dbGetItemWithKey:key excludeInlineData:NO];
if (item) {
//** 數據庫存在記錄 **
// 更新操作時間
[self _dbUpdateAccessTimeWithKey:key];
if (item.filename) {
//** 存在文件名 **
// 讀入文件
item.value = [self _fileReadWithName:item.filename];
if (!item.value) {
//** 未找到文件 **
// 刪除數據庫記錄
[self _dbDeleteItemWithKey:key];
item = nil;
}
}
}
return item;
}
// 數據庫查詢
- (YYKVStorageItem *)_dbGetItemWithKey:(NSString *)key excludeInlineData:(BOOL)excludeInlineData {
// 准備執行sql
NSString *sql = excludeInlineData ? @"select key, filename, size, modification_time, last_access_time, extended_data from manifest where key = ?1;" : @"select key, filename, size, inline_data, modification_time, last_access_time, extended_data from manifest where key = ?1;";
sqlite3_stmt *stmt = [self _dbPrepareStmt:sql];
if (!stmt) return nil;
// 綁定參數
sqlite3_bind_text(stmt, 1, key.UTF8String, -1, NULL);
YYKVStorageItem *item = nil;
// 執行操作
int result = sqlite3_step(stmt);
if (result == SQLITE_ROW) {
//** 存在可讀的row **
// 獲取YYKVStorageItem
item = [self _dbGetItemFromStmt:stmt excludeInlineData:excludeInlineData];
} else {
if (result != SQLITE_DONE) {
//** 未完成執行數據庫 **
// 輸出錯誤logs
if (_errorLogsEnabled) NSLog(@"%s line:%d sqlite query error (%d): %s", __FUNCTION__, __LINE__, result, sqlite3_errmsg(_db));
}
}
return item;
}
// 轉換模型YYKVStorageItem
- (YYKVStorageItem *)_dbGetItemFromStmt:(sqlite3_stmt *)stmt excludeInlineData:(BOOL)excludeInlineData {
int i = 0;
// 取出數據
char *key = (char *)sqlite3_column_text(stmt, i++);
char *filename = (char *)sqlite3_column_text(stmt, i++);
int size = sqlite3_column_int(stmt, i++);
const void *inline_data = excludeInlineData ? NULL : sqlite3_column_blob(stmt, i);
int inline_data_bytes = excludeInlineData ? 0 : sqlite3_column_bytes(stmt, i++);
int modification_time = sqlite3_column_int(stmt, i++);
int last_access_time = sqlite3_column_int(stmt, i++);
const void *extended_data = sqlite3_column_blob(stmt, i);
int extended_data_bytes = sqlite3_column_bytes(stmt, i++);
// 賦值模型
YYKVStorageItem *item = [YYKVStorageItem new];
if (key) item.key = [NSString stringWithUTF8String:key];
if (filename && *filename != 0) item.filename = [NSString stringWithUTF8String:filename];
item.size = size;
if (inline_data_bytes > 0 && inline_data) item.value = [NSData dataWithBytes:inline_data length:inline_data_bytes];
item.modTime = modification_time;
item.accessTime = last_access_time;
if (extended_data_bytes > 0 && extended_data) item.extendedData = [NSData dataWithBytes:extended_data length:extended_data_bytes];
return item;
}YYDiskCache
1.初始化
- (instancetype)initWithPath:(NSString *)path
inlineThreshold:(NSUInteger)threshold {
self = [super init];
if (!self) return nil;
// 1.根據path先從緩存裡面找YYDiskCache(未找到再去重新創建實例)
YYDiskCache *globalCache = _YYDiskCacheGetGlobal(path);
if (globalCache) return globalCache;
// 2.重新創建實例
// 2.1緩存方式
YYKVStorageType type;
if (threshold == 0) {
type = YYKVStorageTypeFile;
} else if (threshold == NSUIntegerMax) {
type = YYKVStorageTypeSQLite;
} else {
type = YYKVStorageTypeMixed;
}
// 2.2實例化YYKVStorage對象(YYKVStorage上面已分析,YYDiskCache的緩存實現都在YKVStorage)
YYKVStorage *kv = [[YYKVStorage alloc] initWithPath:path type:type];
if (!kv) return nil;
// 2.3初始化數據
_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];
// 2.4緩存self
_YYDiskCacheSetGlobal(self);
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_appWillBeTerminated) name:UIApplicationWillTerminateNotification object:nil];
return self;
}
// 獲取已經緩存的YYDiskCache對象
static YYDiskCache *_YYDiskCacheGetGlobal(NSString *path) {
if (path.length == 0) return nil;
// 初始化字典(用來緩存YYDiskCache對象)與創建鎖
_YYDiskCacheInitGlobal();
// 加鎖
dispatch_semaphore_wait(_globalInstancesLock, DISPATCH_TIME_FOREVER);
id cache = [_globalInstances objectForKey:path];
// 解鎖
dispatch_semaphore_signal(_globalInstancesLock);
return cache;
}
// 初始化字典(用來緩存YYDiskCache對象)與創建鎖
static void _YYDiskCacheInitGlobal() {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// 創建一把鎖
_globalInstancesLock = dispatch_semaphore_create(1);
// 初始化字典
_globalInstances = [[NSMapTable alloc] initWithKeyOptions:NSPointerFunctionsStrongMemory valueOptions:NSPointerFunctionsWeakMemory capacity:0];
});
}
// 保存新的YYDiskCache
static void _YYDiskCacheSetGlobal(YYDiskCache *cache) {
if (cache.path.length == 0) return;
_YYDiskCacheInitGlobal();
dispatch_semaphore_wait(_globalInstancesLock, DISPATCH_TIME_FOREVER);
[_globalInstances setObject:cache forKey:cache.path];
dispatch_semaphore_signal(_globalInstancesLock);
}2.添加緩存
// 添加緩存 - (void)setObject:(id)object forKey:(NSString *)key { if (!key) return; if (!object) { //** 緩存對象為null ** // 刪除緩存 [self removeObjectForKey:key]; return; } NSData *extendedData = [YYDiskCache getExtendedDataFromObject:object]; NSData *value = nil; // 你可以customArchiveBlock外部歸檔數據 if (_customArchiveBlock) { value = _customArchiveBlock(object); } else { @try { // 歸檔數據 value = [NSKeyedArchiver archivedDataWithRootObject:object]; } @catch (NSException *exception) { // nothing to do... } } if (!value) return; NSString *filename = nil; if (_kv.type != YYKVStorageTypeSQLite) { // ** 緩存類型非YYKVStorageTypeSQLite ** if (value.length > _inlineThreshold) { // ** 緩存對象大於_inlineThreshold值則用文件緩存 ** // 生成文件名 filename = [self _filenameForKey:key]; } } // 加鎖 Lock(); // 緩存數據(此方法上面講過) [_kv saveItemWithKey:key value:value filename:filename extendedData:extendedData]; // 解鎖 Unlock(); }
3.其他查找,刪除都類似,就不一一分析了