在上一篇文章 中,我们对YYCache的初始化操作了做了简单分析,具体代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 - (instancetype )initWithPath:(NSString *)path { if (path.length == 0 ) return nil ; YYDiskCache *diskCache = [[YYDiskCache alloc] initWithPath:path]; if (!diskCache) return nil ; NSString *name = [path lastPathComponent]; YYMemoryCache *memoryCache = [YYMemoryCache new]; memoryCache.name = name; self = [super init]; _name = name; _diskCache = diskCache; _memoryCache = memoryCache; return self ; }
本篇文章我们介绍一下 YYDiskCache 磁盘缓存的实现
YYDiskCache 简介 先来看一下官方介绍(可在源码中查阅):
1 2 3 4 5 6 7 8 `YYDiskCache` 是一个`线程安全`的,用于存储由`SQLite`支持的键值对和`文件系统`(类似于 `NSURLCache` 的磁盘缓存) - 使用`LRU(least-recently-used)`来移除对象; - 可以通过 `cost`,`count` 和 `age` 来控制; - 可以配置为当`没有空闲磁盘空间`时`自动删除对象`; - 可以`自动决定`每个对象的`存储类型(sqlite/file)`,以获得更好的性能; 在iOS系统上可以直接从官网下载最新的 `SQLite` 源码编译编译并忽略系统的`libsqlite3.dylib`可以获得`2x~4x`的速度。
我们对上面的信息进行提炼一下关键信息:
① 线程安全;
② 采用LRU移除对象;
③ 多维度的控制: cost,count 和 age ;
④ 不同数据自动采用不同的存储机制:sqlite 或 file;
⑤ 磁盘不足时可自动删除;
⑥ 支持同步与异步的方式调用(源码API层面);
在提到YYCache的LRU时,网上大部分的文章都是再谈双链表 + hash表,该结构只是YYCache内存缓存(YYMemoryCache) 所采用的LRU方案,我们需要知道:YYCache 的磁盘缓存(YYDiskCache)也是支持LRU的;
YYDiskCache 源码总览 YYDiskCache 初始化 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 @interface YYDiskCache : NSObject @property (nullable , copy ) NSString *name;@property (readonly ) NSString *path;@property (readonly ) NSUInteger inlineThreshold;······ - (nullable instancetype )initWithPath:(NSString *)path inlineThreshold:(NSUInteger )threshold NS_DESIGNATED_INITIALIZER ; ······ @end
初始化YYCache时调用了 YYDiskCache 的 initWithPath 方法
这里主要是对 inlineThreshold 阈值进行了初始化(20KB)
至于为何是20KB,我们可以参看YYCache 设计思路
和 SQLite官方说明
1 2 3 - (instancetype )initWithPath:(NSString *)path { return [self initWithPath:path inlineThreshold:1024 * 20 ]; }
真正的初始化方法NS_DESIGNATED_INITIALIZER
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 - (instancetype )initWithPath:(NSString *)path inlineThreshold:(NSUInteger )threshold { self = [super init]; if (!self ) return nil ; YYDiskCache *globalCache = _YYDiskCacheGetGlobal(path); if (globalCache) return globalCache; ······ [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector (_appWillBeTerminated) name:UIApplicationWillTerminateNotification object:nil ]; return self ; }
从缓存中获取YYDiskCache对象 在上文中我们得知,YYDiskCache在初始化的时候,首先会根据path 调用 _YYDiskCacheGetGlobal来进行查找,如果查到,就直接返回,如果没有找到就执行一系列的初始化操作,然后又调用 _YYDiskCacheSetGlobal 将创建好的YYDiskCache 对象存入,现在我们来分析一下 _YYDiskCacheGetGlobal 和 _YYDiskCacheSetGlobal;
1 2 3 static NSMapTable *_globalInstances;static dispatch_semaphore_t _globalInstancesLock;
定义了一个全局的 NSMapTable 类型的 _globalInstances 和 一个 dispatch_semaphore_t 类型的 _globalInstancesLock;
_globalInstances:存放所有的 YYDiskCache 对象
dispatch_semaphore_t:用来保证读写YYDiskCache对象的线程安全
_YYDiskCacheInitGlobal 1 2 3 4 5 6 7 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 ]; }); }
使用dispatch_once保证只初始化一次;
🔔❗️❗️❗️ _globalInstances 用来存放所有的 YYDiskCache对象, 使用 NSMapTable + NSPointerFunctionsWeakMemory, 弱引用 内部的 YYDiskCache对象;
🔔❗️❗️❗️ _globalInstancesLock 用来保证读取YYDiskCache对象的线程安全;
_YYDiskCacheGetGlobal 1 2 3 4 5 6 7 8 static YYDiskCache *_YYDiskCacheGetGlobal(NSString *path) { if (path.length == 0 ) return nil; _YYDiskCacheInitGlobal(); dispatch_semaphore_wait(_globalInstancesLock, DISPATCH_TIME_FOREVER); id cache = [_globalInstances objectForKey:path]; dispatch_semaphore_signal(_globalInstancesLock); return cache; }
_YYDiskCacheSetGlobal 1 2 3 4 5 6 7 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); }
在调用_YYDiskCacheGetGlobal 或 _YYDiskCacheSetGlobal 时会调用_YYDiskCacheInitGlobal 进行初始化;
由于_YYDiskCacheInitGlobal内部使用dispatch_once,可保证只初始化了一次;
dispatch_semaphore_t 线程同步方案
1 2 3 4 5 6 _globalInstancesLock = dispatch_semaphore_create(1 ); dispatch_semaphore_wait(_globalInstancesLock, DISPATCH_TIME_FOREVER); dispatch_semaphore_signal(_globalInstancesLock);
真正的创建YYDiskCache对象
根据path 和存储方式 YYKVStorageType 初始化 YYKVStorage;
初始化了一个 dispatch_semaphore 信号量;
初始化了一个 dispatch_queue 自定义的并发队列;
初始化一些额外的控制属性;
将上述初始化好的数据挂载到 YYDiskCache 对象上,并存入全局的 _globalInstances 中;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 - (instancetype )initWithPath:(NSString *)path inlineThreshold:(NSUInteger )threshold { self = [super init]; if (!self ) return nil ; YYDiskCache *globalCache = _YYDiskCacheGetGlobal(path); if (globalCache) return globalCache; YYKVStorageType type; if (threshold == 0 ) { type = YYKVStorageTypeFile; } else if (threshold == NSUIntegerMax ) { type = YYKVStorageTypeSQLite; } else { type = YYKVStorageTypeMixed; } YYKVStorage *kv = [[YYKVStorage alloc] initWithPath:path type:type]; if (!kv) return nil ; _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]; _YYDiskCacheSetGlobal(self ); [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector (_appWillBeTerminated) name:UIApplicationWillTerminateNotification object:nil ]; return self ; }
YYDiskCache 线程安全 在上文 YYDiskCache 初始化的时候,创建了一个 dispatch_semaphore 信号量;我们从API来分析 YYDiskCache 的线程安全;
以 - (void)removeObjectForKey:(NSString *)key 为例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 #define Lock() dispatch_semaphore_wait(self->_lock, DISPATCH_TIME_FOREVER) #define Unlock() dispatch_semaphore_signal(self->_lock) - (void )removeObjectForKey:(NSString *)key { if (!key) return ; Lock(); [_kv removeItemForKey:key]; Unlock(); } - (void )removeObjectForKey:(NSString *)key withBlock:(void (^)(NSString *key))block { __weak typeof (self ) _self = self ; dispatch_async (_queue, ^{ __strong typeof (_self ) self = _self ; [self removeObjectForKey:key]; if (block) block(key); }); }
可以看出真正在操作数据的其实是[_kv removeItemForKey:key];
_kv 就是在初始化 YYDiskCache 时,创建的 YYKVStorage 对象;
初始化 YYDiskCache 时,创建的 dispatch_semaphore 和 dispatch_queue 作用:
dispatch_semaphore 用来保证操作缓存数据时的线程安全;
并发的dispatch_queue 用来实现操作缓存数据时的同步还是异步方式;
我们只是以删除操作为例进行了说明:别的操作类似;
YYDiskCache 存储操作
以 - (void)setObject:(id<NSCoding>)object forKey:(NSString *)key; 为例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 - (void )setObject:(id <NSCoding >)object forKey:(NSString *)key { if (!key) return ; if (!object) { [self removeObjectForKey:key]; return ; } NSData *extendedData = [YYDiskCache getExtendedDataFromObject:object]; NSData *value = nil ; if (_customArchiveBlock) { value = _customArchiveBlock(object); } else { @try { value = [NSKeyedArchiver archivedDataWithRootObject:object]; } @catch (NSException *exception) { } } if (!value) return ; NSString *filename = nil ; if (_kv.type != YYKVStorageTypeSQLite) { if (value.length > _inlineThreshold) { filename = [self _filenameForKey:key]; } } Lock(); [_kv saveItemWithKey:key value:value filename:filename extendedData:extendedData]; Unlock(); }
有上述得知:数据量超过阈值后,会生成一个 filename,我们接着分析;
存储机制:inline_data / file 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 - (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) { if (![self _fileWriteWithName:filename data:value]) { return NO ; } if (![self _dbSaveWithKey:key value:value fileName:filename extendedData:extendedData]) { [self _fileDeleteWithName:filename]; return NO ; } return YES ; } else { if (_type != YYKVStorageTypeSQLite) { NSString *filename = [self _dbGetFilenameWithKey:key]; if (filename) { [self _fileDeleteWithName:filename]; } } return [self _dbSaveWithKey:key value:value fileName:nil extendedData:extendedData]; } }
🔔❗️❗️❗️不管数据量超没超过阈值,都会在 SQLite 中写入一条数据的
超过阈值:SQLite + File;(不将data数据写入SQLite)
没超过阈值:SQLite + inline_data;
提高存储效率;
我们看一下具体源码实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 - (BOOL )_dbSaveWithKey:(NSString *)key value:(NSData *)value fileName:(NSString *)fileName extendedData:(NSData *)extendedData { 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);" ; 🔔❗️❗️❗️🔔❗️❗️❗️🔔❗️❗️❗️ 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 ) { sqlite3_bind_blob(stmt, 4 , value.bytes, (int )value.length, 0 ); } else { 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) { if (_errorLogsEnabled) NSLog (@"%s line:%d sqlite insert error (%d): %s" , __FUNCTION__, __LINE__, result, sqlite3_errmsg(_db)); return NO ; } return YES ; }
SQLite DB 操作 看到这里,大家可能会想,SQLite DB操作无非就是写几行SQL 跑一下而已,有什么可说的。然而并非如此,YYCache 同样做了很多提高性能的事情!
sqlite3_stmt 大家都知道sqlite3 有一个 执行的 SQL 语句的函数sqlite3_exec:
1 2 3 4 5 6 7 SQLITE_API int sqlite3_exec ( sqlite3*, const char *sql, int (*callback)(void *,int ,char **,char **), void *, char **errmsg ) ;
其实呢:SQL语句可以理解为一种编程语言的源代码,而想要执行这个源代码就必须要进行编译/解析,而sqlite3_stmt是一个预编译语句对象, 该对象的一个实例表示一条SQL语句,并且已经被编译成二进制形式,可以直接运行;
sqlite3_stmt 的使用流程:
① 使用sqlite3_prepare_v2()创建预处理语句对象;
② 使用sqlite3_bind()将值绑定到SQL上;
③ 通过调用sqlite3_step()一次或多次运行SQL;
④ 使用sqlite3_reset()重置准备好的语句,然后返回到步骤2。这样做0次或更多次。
⑤ 使用sqlite3_finalize()销毁对象。
YYCache 只有在初始化DB(- (BOOL)_dbInitialize;)时使用了sqlite3_exec执行SQL,而重复性的增删改查操作都是使用sqlite3_stmt来执行SQL;
缓存 SQL 操作 接着回到我们的源码:
1 2 sqlite3_stmt *stmt = [self _dbPrepareStmt:sql];
采用 CFMutableDictionaryRef 缓存 sqlite3_stmt 对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 - (sqlite3_stmt *)_dbPrepareStmt:(NSString *)sql { if (![self _dbCheck] || sql.length == 0 || !_dbStmtCache) return NULL ; sqlite3_stmt *stmt = (sqlite3_stmt *)CFDictionaryGetValue (_dbStmtCache, (__bridge const void *)(sql)); if (!stmt) { int result = sqlite3_prepare_v2(_db, sql.UTF8String, -1 , &stmt, NULL ); if (result != SQLITE_OK) { if (_errorLogsEnabled) NSLog (@"%s line:%d sqlite stmt prepare error (%d): %s" , __FUNCTION__, __LINE__, result, sqlite3_errmsg(_db)); return NULL ; } CFDictionarySetValue (_dbStmtCache, (__bridge const void *)(sql), stmt); } else { sqlite3_reset(stmt); } return stmt; }
[self _dbCheck]其实是对DB数据库的一个校验与重试处理:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 static const NSUInteger kMaxErrorRetryCount = 8 ;static const NSTimeInterval kMinRetryTimeInterval = 2.0 ;- (BOOL )_dbCheck { if (!_db) { if (_dbOpenErrorCount < kMaxErrorRetryCount && CACurrentMediaTime () - _dbLastOpenErrorTime > kMinRetryTimeInterval) { return [self _dbOpen] && [self _dbInitialize]; } else { return NO ; } } return YES ; }
[self _dbOpen]内部会调用sqlite3_open打开数据库,打开成功后会创建了一个_dbStmtCache,用来缓存sqlite3_stmt对象;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 - (BOOL )_dbOpen { if (_db) return YES ; int result = sqlite3_open(_dbPath.UTF8String, &_db); if (result == SQLITE_OK) { CFDictionaryKeyCallBacks keyCallbacks = kCFCopyStringDictionaryKeyCallBacks; CFDictionaryValueCallBacks valueCallbacks = {0 }; _dbStmtCache = CFDictionaryCreateMutable (CFAllocatorGetDefault (), 0 , &keyCallbacks, &valueCallbacks); _dbLastOpenErrorTime = 0 ; _dbOpenErrorCount = 0 ; return YES ; } else { _db = NULL ; if (_dbStmtCache) CFRelease (_dbStmtCache); _dbStmtCache = NULL ; _dbLastOpenErrorTime = CACurrentMediaTime (); _dbOpenErrorCount++; if (_errorLogsEnabled) { NSLog (@"%s line:%d sqlite open failed (%d)." , __FUNCTION__, __LINE__, result); } return NO ; } }
sqlite3 WAL
WAL的全称是Write Ahead Logging,它是很多数据库中用于实现原子事务的一种机制,SQLite在3.7.0版本引入了该特性。
在引入WAL机制之前,SQLite使用rollback journal机制实现原子事务。
rollback journal VS WAL
rollback journal机制:修改数据之前,先对要修改的数据进行备份,如果事务成功,就提交修改并删除备份;如果事务失败:就将备份数据拷贝回去,撤销修改;
WAL机制:当修改数据时,并不直接写入数据库,而是写入到另外一个WAL文件中;如果事务成功:将会在随后的某个时间节点写回到数据库;如果事务失败:WAL文件中的记录会被忽略;
同步WAL文件和数据库文件的行为称为checkpoint,它有SQLite自动执行,默认:WAL文件累计到1000页修改;
也可以通过SQLITE_API int sqlite3_wal_checkpoint(sqlite3 *db, const char *zDb);手动执行并重置WAL;
可以在 SQLite官方文档 查阅相关使用介绍:
SQL语句中使用
1 2 3 / / journal_mode 模式;/ / 比如:PRAGMA journal_mode = wal;PRAGMA journal_mode
1 PRAGMA wal_autocheckpoint
函数调用
1 2 3 4 5 SQLITE_API int sqlite3_wal_checkpoint ( sqlite3 *db, const char *zDb ) ;
1 2 3 4 5 6 SQLITE_API int sqlite3_wal_autocheckpoint ( sqlite3 *db, int N ) ;
1 2 3 4 5 6 SQLITE_API void *sqlite3_wal_hook( sqlite3*, int (*)(void *,sqlite3*,const char *,int ), void * );
YYDiskCache 中的 SQLite WAL
1 2 3 4 - (BOOL )_dbInitialize { NSString *sql = @"pragma journal_mode = wal; pragma synchronous = normal; create table if not exists manifest (key text, filename text, size integer, inline_data blob, modification_time integer, last_access_time integer, extended_data blob, primary key(key)); create index if not exists last_access_time_idx on manifest(last_access_time);" ; return [self _dbExecute:sql]; }
1 2 3 4 5 - (void )_dbCheckpoint { if (![self _dbCheck]) return ; sqlite3_wal_checkpoint(_db, NULL ); }
在YYKVStorage.m中的部分remove操作中使用到了_dbCheckpoint
YYDiskCache LRU
以 - (void)trimToCount:(NSUInteger)count; 为例:
1 2 3 4 5 6 - (void )trimToCount:(NSUInteger )count { Lock(); [self _trimToCount:count]; Unlock(); }
1 2 3 4 5 - (void )_trimToCount:(NSUInteger )countLimit { if (countLimit >= INT_MAX) return ; [_kv removeItemsToFitCount:(int )countLimit]; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 - (BOOL )removeItemsToFitCount:(int )maxCount { if (maxCount == INT_MAX) return YES ; if (maxCount <= 0 ) return [self removeAllItems]; int total = [self _dbGetTotalItemCount]; if (total < 0 ) return NO ; if (total <= maxCount) return YES ; NSArray *items = nil ; BOOL suc = NO ; do { int perCount = 16 ; items = [self _dbGetItemSizeInfoOrderByTimeAscWithLimit:perCount]; for (YYKVStorageItem *item in items) { if (total > maxCount) { if (item.filename) { [self _fileDeleteWithName:item.filename]; } suc = [self _dbDeleteItemWithKey:item.key]; total--; } else { break ; } if (!suc) break ; } } while (total > maxCount && items.count > 0 && suc); if (suc) [self _dbCheckpoint]; return suc; }
① 定义每次从 SQLite DB 取出的数据量为:16个
② 从 SQLite DB 取出相应数量的数据
③ 对取出的数据进行遍历删除操作,直到符合删减的要求
③-① 如果是File方式,还需要将File一并删除
③-② 删除SQLite DB中的数据
由上可知:LRU 应该在 ② 从 SQLite DB 取出相应数量的数据; 中有所体现;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 - (NSMutableArray *)_dbGetItemSizeInfoOrderByTimeAscWithLimit:(int )count { NSString *sql = @"select key, filename, size from manifest order by last_access_time asc limit ?1;" ; sqlite3_stmt *stmt = [self _dbPrepareStmt:sql]; if (!stmt) return nil ; sqlite3_bind_int(stmt, 1 , count); NSMutableArray *items = [NSMutableArray new]; do { int result = sqlite3_step(stmt); if (result == SQLITE_ROW) { char *key = (char *)sqlite3_column_text(stmt, 0 ); char *filename = (char *)sqlite3_column_text(stmt, 1 ); int size = sqlite3_column_int(stmt, 2 ); NSString *keyStr = key ? [NSString stringWithUTF8String:key] : nil ; if (keyStr) { YYKVStorageItem *item = [YYKVStorageItem new]; item.key = key ? [NSString stringWithUTF8String:key] : nil ; item.filename = filename ? [NSString stringWithUTF8String:filename] : nil ; item.size = size; [items addObject:item]; } } else if (result == SQLITE_DONE) { break ; } else { if (_errorLogsEnabled) NSLog (@"%s line:%d sqlite query error (%d): %s" , __FUNCTION__, __LINE__, result, sqlite3_errmsg(_db)); items = nil ; break ; } } while (1 ); return items; }
执行的SQL语句是:select key, filename, size from manifest order by last_access_time asc limit ?1;
按照last_access_time升序排列依次取出;
同样使用sqlite3_stmt执行SQL;
那么last_access_time升序和LRU有什么关系呢?
那我们就不得不在提一个AP,以- (nullable id<NSCoding>)objectForKey:(NSString *)key;为例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 - (id <NSCoding >)objectForKey:(NSString *)key { if (!key) return nil ; Lock(); YYKVStorageItem *item = [_kv getItemForKey:key]; Unlock(); if (!item.value) return nil ; id object = nil ; if (_customUnarchiveBlock) { object = _customUnarchiveBlock(item.value); } else { @try { object = [NSKeyedUnarchiver unarchiveObjectWithData:item.value]; } @catch (NSException *exception) { } } if (object && item.extendedData) { [YYDiskCache setExtendedData:item.extendedData toObject:object]; } return object; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 - (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; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 - (BOOL )_dbUpdateAccessTimeWithKey:(NSString *)key { NSString *sql = @"update manifest set last_access_time = ?1 where key = ?2;" ; sqlite3_stmt *stmt = [self _dbPrepareStmt:sql]; if (!stmt) return NO ; sqlite3_bind_int(stmt, 1 , (int )time(NULL )); sqlite3_bind_text(stmt, 2 , key.UTF8String, -1 , NULL ); int result = sqlite3_step(stmt); if (result != SQLITE_DONE) { if (_errorLogsEnabled) NSLog (@"%s line:%d sqlite update error (%d): %s" , __FUNCTION__, __LINE__, result, sqlite3_errmsg(_db)); return NO ; } return YES ; }
执行的SQL语句是:update manifest set last_access_time = ?1 where key = ?2;
更新本条数据的 last_access_time;
同样使用sqlite3_stmt执行SQL;
至此:YYDiskCache 中关于 LRU 的实现也就明朗了~
get 操作:
每次在读取数据的时候,都会更新数据的last_access_time;
trim 操作
① 当我们删减数据时,根据last_access_time升序取出一定数量(每次16个)的数据;
② 对取出的数据分别进行删除操作;
③ 删除过程中,判断是否已经满足删减要求;未满足的话继续从①开始,直到满足删减要求;