开源代码信息
YYCache 开源库地址
我们在分析YTKNetWork,SDWebImage的时候涉及到缓存的实现,缓存一般都包含内存缓存和磁盘缓存,并且提供磁盘空间控制,缓存内容有效期控制等功能,我们接下来也从这几个方面对YYCache进行分析。YYCache代码量不多很精简,建议大家都可以尝试阅读下,因为一旦看惯代码了,就不会觉得畏惧了。下面先给出一个之前已经绘制好的一个YYCache构成图:
整个开源库也只有8个文件四个类:
YYCache: YYCache 的顶层类,业务主要和它进行交互获取实现对应的缓存任务。
YYMemoryCache: 内存缓存,它是基于双向链表结构,它和NSCache的区别在于,YYMemoryCache 剔除过期对象是基于LRU方法,而NSCache剔除对象的方法是不确定的。
YYMemoryCache可以根据缓存的cost,count,age来控制缓存,而NSCache不能。在收到内存缓存不足警告,或者应用进入后台的时候可以指定剔除某些缓存。
YYDiskCache: 它是基于文件以及sqlite数据的磁盘缓存,它会根据不同的情况自动选择使用文件还是数据库来存储缓存,和YYMemoryCache类似它也是给予LRU原则来移除无用的缓存的,也可以根据缓存的cost,count,age来控制缓存。并且能够在磁盘空间不足的时候自动剔除无用的数据。
YYKVStorage 是YYDiskCache执行存储的重要对象。后面介绍源码的时候我们会详细看下这部分实现。
我们先来看下YYCache,YYCache我们这里只会做简单介绍,因为它大部分的功能都依托于YYMemoryCache和YYDiskCache。它使用YYMemoryCache用于存储小而快的内存缓存,使用YYDiskCache来存储大的对象。它提供了对缓存进行增删改查的操作,并且每种操作都提供了同步和异步两种方式。
这里仅仅列出同步的增删改查代码其实上层的逻辑和其他的缓存没有太多的差别,重点是底层的实现:
- (void)setObject:(id<NSCoding>)object forKey:(NSString *)key { [_memoryCache setObject:object forKey:key]; [_diskCache setObject:object forKey:key]; }
- (void)removeObjectForKey:(NSString *)key { [_memoryCache removeObjectForKey:key]; [_diskCache removeObjectForKey: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; }
- (BOOL)containsObjectForKey:(NSString *)key { return [_memoryCache containsObjectForKey:key] || [_diskCache containsObjectForKey:key]; }
|
YYMemoryCache
YYMemoryCache 内部是基于 _YYLinkedMap 双向链表实现的。我们看下YYMemoryCache初始化:
- (instancetype)init { self = super.init; pthread_mutex_init(&_lock, NULL); _lru = [_YYLinkedMap new]; _queue = dispatch_queue_create("com.ibireme.cache.memory", DISPATCH_QUEUE_SERIAL); _countLimit = NSUIntegerMax; _costLimit = NSUIntegerMax; _ageLimit = DBL_MAX; _autoTrimInterval = 5.0; _shouldRemoveAllObjectsOnMemoryWarning = YES; _shouldRemoveAllObjectsWhenEnteringBackground = YES; [[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; }
|
_YYLinkedMap是一个双向链表,这部分代码不展开介绍,但是给出了代码的相关注释:
- (void)insertNodeAtHead:(_YYLinkedMapNode *)node { CFDictionarySetValue(_dic, (__bridge const void *)(node->_key), (__bridge const void *)(node)); _totalCost += node->_cost; _totalCount++; if (_head) { node->_next = _head; _head->_prev = node; _head = node; } else { _head = _tail = node; } }
- (void)bringNodeToHead:(_YYLinkedMapNode *)node { if (_head == node) return; if (_tail == node) { _tail = node->_prev; _tail->_next = nil; } else { node->_next->_prev = node->_prev; node->_prev->_next = node->_next; } node->_next = _head; node->_prev = nil; _head->_prev = node; _head = node; }
- (void)removeNode:(_YYLinkedMapNode *)node { CFDictionaryRemoveValue(_dic, (__bridge const void *)(node->_key)); _totalCost -= node->_cost; _totalCount--; if (node->_next) node->_next->_prev = node->_prev; if (node->_prev) node->_prev->_next = node->_next; if (_head == node) _head = node->_next; if (_tail == node) _tail = node->_prev; }
- (_YYLinkedMapNode *)removeTailNode { if (!_tail) return nil; _YYLinkedMapNode *tail = _tail; CFDictionaryRemoveValue(_dic, (__bridge const void *)(_tail->_key)); _totalCost -= _tail->_cost; _totalCount--; if (_head == _tail) { _head = _tail = nil; } else { _tail = _tail->_prev; _tail->_next = nil; } return tail; }
- (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); }); } else if (_releaseOnMainThread && !pthread_main_np()) { dispatch_async(dispatch_get_main_queue(), ^{ CFRelease(holder); }); } else { CFRelease(holder); } } }
|
增/改缓存
- (void)setObject:(id)object forKey:(id)key withCost:(NSUInteger)cost { if (!object) { [self removeObjectForKey:key]; return; } pthread_mutex_lock(&_lock); _YYLinkedMapNode *node = CFDictionaryGetValue(_lru->_dic, (__bridge const void *)(key)); NSTimeInterval now = CACurrentMediaTime();
if (node) { _lru->_totalCost -= node->_cost; _lru->_totalCost += cost; node->_cost = cost; node->_time = now; node->_value = object; [_lru bringNodeToHead:node]; } else { node = [_YYLinkedMapNode new]; node->_cost = cost; node->_time = now; node->_key = key; node->_value = object; [_lru insertNodeAtHead:node]; } if (_lru->_totalCost > _costLimit) { dispatch_async(_queue, ^{ [self trimToCost:_costLimit]; }); } 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]; }); } else if (_lru->_releaseOnMainThread && !pthread_main_np()) { dispatch_async(dispatch_get_main_queue(), ^{ [node class]; }); } } pthread_mutex_unlock(&_lock); }
|
setObject方法会首先检查传入的当前对象是否是nil,如果是nil会从缓存链表中删除传入key的元素。如果不是nil,则会在缓存链表中查看是否已经存在,如果是的话,就使用传入的object更新节点内容,然后将其移到链表的最前面。如果不存在就新建一个node插入到缓存链表头部,然后会对链表的空间以及链表节点数量进行检查,剔除掉不用的对象。
删除缓存
这个不做过多介绍大家直接看代码:
- (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]; }); } else if (_lru->_releaseOnMainThread && !pthread_main_np()) { dispatch_async(dispatch_get_main_queue(), ^{ [node class]; }); } } pthread_mutex_unlock(&_lock); }
|
查找缓存
- (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; }
- (BOOL)containsObjectForKey:(id)key { if (!key) return NO; pthread_mutex_lock(&_lock); BOOL contains = CFDictionaryContainsKey(_lru->_dic, (__bridge const void *)(key)); pthread_mutex_unlock(&_lock); return contains; }
|
这部分最关键的是它会在每次查找缓存,找到的时候将找到的对象移动到缓存链表最前面。
缓存清理
我们上面提到了在初始化的时候会触发定期清理缓存操作,默认每隔5秒进行一次。并且在每次添加缓存的时候还会触发一次。
- (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) { 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 { usleep(10 * 1000); } } if (holder.count) { dispatch_queue_t queue = _lru->_releaseOnMainThread ? dispatch_get_main_queue() : YYMemoryCacheGetReleaseQueue(); dispatch_async(queue, ^{ [holder count]; }); } }
|
_trimToCost 中如果有超过限制就会不断从缓存链表队尾移除一个对象,然后先将待移除的对象暂时添加到holder,避免频繁对内存读写,导致锁被占用,直到没有超过限制的时候在对应的队列中将这些对象的空间释放。****_trimToCount,_trimToAge**** 和 _trimToCost 代码类似,只不过判断的标准不一样而已,这里就不展开介绍了,有兴趣的同学可以查看对应的代码。
YYDiskCache
磁盘缓存和内存缓存对外的接口大体类似,只有一点不大一致的:
它多出了inlineThreshold属性,它是一个阈值,最开始的时候磁盘缓存是存储在sqlite上的,一旦存储在sqlite的磁盘缓存超过这个值的时候后续的对象就会被存储到文件,如果设置为0的话表示所有的缓存都会被存储到文件,NSUIntegerMax表示所有的对象都会被存储到sqlite,默认这个阈值是20KB
@property (readonly) NSUInteger inlineThreshold;
|
多出了归档对象的方式,默认情况下YYCache是通过NSKeyedArchiver和NSKeyedUnarchiver进行归档和反归档,但是这要求对象必须遵循NSCoding协议,如果某些对象没法做到这一点,那么可以通过指定customArchiveBlock以及customUnarchiveBlock来自定义归档和反归档将要存储的对象:
@property (nullable, copy) NSData *(^customArchiveBlock)(id object); @property (nullable, copy) id (^customUnarchiveBlock)(NSData *data);
|
默认缓存文件名是通过md5(key)计算出来的,但是我们可以通过customFileNameBlock来自定义key与对它对应的缓存文件名字的命名规范。
@property (nullable, copy) NSString *(^customFileNameBlock)(NSString *key);
|
在对缓存对象进行缓存之前,还可以通过下面的方法对缓存数据进行扩展,它实际上是通过关联对象的方式来实现的。
+ (nullable NSData *)getExtendedDataFromObject:(id)object; + (void)setExtendedData:(nullable NSData *)extendedData toObject:(id)object;
|
我们接下来开始分析YYDiskCache
YYDiskCache的初始化
- (instancetype)initWithPath:(NSString *)path { return [self initWithPath:path inlineThreshold:1024 * 20]; }
- (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的实例以path为key,缓存到类型为NSMapTable的_globalInstances中。所以我们在最初的时候会先从globalInstances中先看下是否有已经创建好的,如果没有再新建YYDiskCache并缓存起来。YYDiskCache中最关键的就是YYKVStorage了,所有的磁盘存储都由YYKVStorage来完成。
YYDiskCache上层比较简单,主要的逻辑在YYKVStorage,但是在介绍YYKVStorage之前我们先简单浏览下YYDiskCache:
增/改缓存
- (void)setObject:(id<NSCoding>)object forKey:(NSString *)key { 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(); }
|
上面代码注释得比较详细了,大家看代码就能看懂,就不展开了。
删除缓存
- (void)removeObjectForKey:(NSString *)key { if (!key) return; Lock(); [_kv removeItemForKey:key]; Unlock(); }
|
查找缓存
- (BOOL)containsObjectForKey:(NSString *)key { if (!key) return NO; Lock(); BOOL contains = [_kv itemExistsForKey:key]; Unlock(); return contains; }
- (void)containsObjectForKey:(NSString *)key withBlock:(void(^)(NSString *key, BOOL contains))block { if (!block) return; __weak typeof(self) _self = self; dispatch_async(_queue, ^{ __strong typeof(_self) self = _self; BOOL contains = [self containsObjectForKey:key]; block(key, contains); }); }
- (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; }
|
还是不大想解释,代码不难。
缓存清理
- (void)_trimToCost:(NSUInteger)costLimit { if (costLimit >= INT_MAX) return; [_kv removeItemsToFitSize:(int)costLimit]; }
- (void)_trimToCount:(NSUInteger)countLimit { if (countLimit >= INT_MAX) return; [_kv removeItemsToFitCount:(int)countLimit]; }
- (void)_trimToAge:(NSTimeInterval)ageLimit { if (ageLimit <= 0) { [_kv removeAllItems]; return; } long timestamp = time(NULL); if (timestamp <= ageLimit) return; long age = timestamp - ageLimit; if (age >= INT_MAX) return; [_kv removeItemsEarlierThanTime:(int)age]; }
|
YYKVStorage
存储对象
- (BOOL)saveItemWithKey:(NSString *)key value:(NSData *)value filename:(NSString *)filename extendedData:(NSData *)extendedData { 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]; } }
|
saveItemWithKey方法中会先判断filename是否为空,如果是有指定缓存文件名,那么就会先将缓存数据写入到文件缓存起来,同时将缓存记录写入到数据库中,作为缓存记录。如果filename为nil,那么就直接将缓存数据缓存到数据库inline_data字段。接下来我们来看下_dbSaveWithKey:
- (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; }
|
我们看这个方法主要是了解这个缓存记录表的结构设计:这个缓存记录表有7个字段分别列举如下:
删除对象
- (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)removeItemsLargerThanSize:(int)size { if (size == INT_MAX) return YES; if (size <= 0) return [self removeAllItems]; switch (_type) { case YYKVStorageTypeSQLite: { if ([self _dbDeleteItemsWithSizeLargerThan:size]) { [self _dbCheckpoint]; return YES; } } break; case YYKVStorageTypeFile: case YYKVStorageTypeMixed: { NSArray *filenames = [self _dbGetFilenamesWithSizeLargerThan:size]; for (NSString *name in filenames) { [self _fileDeleteWithName:name]; } if ([self _dbDeleteItemsWithSizeLargerThan:size]) { [self _dbCheckpoint]; return YES; } } break; } return NO; }
- (BOOL)removeItemsEarlierThanTime:(int)time { if (time <= 0) return YES; if (time == INT_MAX) return [self removeAllItems]; switch (_type) { case YYKVStorageTypeSQLite: { if ([self _dbDeleteItemsWithTimeEarlierThan:time]) { [self _dbCheckpoint]; return YES; } } break; case YYKVStorageTypeFile: case YYKVStorageTypeMixed: { NSArray *filenames = [self _dbGetFilenamesWithTimeEarlierThan:time]; for (NSString *name in filenames) { [self _fileDeleteWithName:name]; } if ([self _dbDeleteItemsWithTimeEarlierThan:time]) { [self _dbCheckpoint]; return YES; } } break; } return NO; }
|
- (BOOL)removeItemsToFitSize:(int)maxSize { if (maxSize == INT_MAX) return YES; if (maxSize <= 0) return [self removeAllItems]; int total = [self _dbGetTotalItemSize]; if (total < 0) return NO; if (total <= maxSize) return YES; NSArray *items = nil; BOOL suc = NO; do { int perCount = 16; items = [self _dbGetItemSizeInfoOrderByTimeAscWithLimit:perCount]; for (YYKVStorageItem *item in items) { if (total > maxSize) { if (item.filename) { [self _fileDeleteWithName:item.filename]; } suc = [self _dbDeleteItemWithKey:item.key]; total -= item.size; } else { break; } if (!suc) break; } } while (total > maxSize && items.count > 0 && suc); if (suc) [self _dbCheckpoint]; return suc; }
- (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; }
|
获取对象
这里也不想介绍了,大家看注释吧,其实了解了数据库表结构,以及存储的思路,删除和获取其实很好理解的。
- (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 *)getItemInfoForKey:(NSString *)key { if (key.length == 0) return nil; YYKVStorageItem *item = [self _dbGetItemWithKey:key excludeInlineData:YES]; return item; }
- (NSData *)getItemValueForKey:(NSString *)key { if (key.length == 0) return nil; NSData *value = nil; switch (_type) { case YYKVStorageTypeFile: { NSString *filename = [self _dbGetFilenameWithKey:key]; if (filename) { value = [self _fileReadWithName:filename]; if (!value) { [self _dbDeleteItemWithKey:key]; value = nil; } } } break; case YYKVStorageTypeSQLite: { value = [self _dbGetValueWithKey:key]; } break; case YYKVStorageTypeMixed: { NSString *filename = [self _dbGetFilenameWithKey:key]; if (filename) { value = [self _fileReadWithName:filename]; if (!value) { [self _dbDeleteItemWithKey:key]; value = nil; } } else { value = [self _dbGetValueWithKey:key]; } } break; } if (value) { [self _dbUpdateAccessTimeWithKey:key]; } return value; }
|
最后看下磁盘缓存的目录结构:
/path/ /manifest.sqlite /manifest.sqlite-shm /manifest.sqlite-wal /data/ /e10adc3949ba59abbe56e057f20f883e /e10adc3949ba59abbe56e057f20f883e /trash/ /unused_file_or_folder
|
manifest.sqlite,manifest.sqlite-shm,manifest.sqlite-wal 是数据库缓存记录相关的sqlite数据库文件,/data/目录是用于存储对应的文件缓存,/trash/则是和我们电脑的垃圾箱一样。
总结
其实大家看文章开始的那张图就啥都明白了,不明白抽我,抖机~~~~~~