开源代码信息

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);
//创建_YYLinkedMap
_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];

//间隔_autoTrimInterval时间定期清除过期,超过限制的缓存
[self _trimRecursively];
return self;
}

_YYLinkedMap是一个双向链表,这部分代码不展开介绍,但是给出了代码的相关注释:

- (void)insertNodeAtHead:(_YYLinkedMapNode *)node {
CFDictionarySetValue(_dic, (__bridge const void *)(node->_key), (__bridge const void *)(node));
//计算_totalCost
_totalCost += node->_cost;
//计算_totalCount
_totalCount++;
if (_head) {
//将node插入到head之前
node->_next = _head;
_head->_prev = node;
_head = node;
} else {
//空的双向链表
_head = _tail = node;
}
}

- (void)bringNodeToHead:(_YYLinkedMapNode *)node {
if (_head == node) return;

// 将node从原来的双向链表中分离开来
if (_tail == node) {
_tail = node->_prev;
_tail->_next = nil;
} else {
node->_next->_prev = node->_prev;
node->_prev->_next = node->_next;
}
//将node插入到head
node->_next = _head;
node->_prev = nil;
_head->_prev = node;
_head = node;
}

- (void)removeNode:(_YYLinkedMapNode *)node {
CFDictionaryRemoveValue(_dic, (__bridge const void *)(node->_key));
//计算_totalCost
_totalCost -= node->_cost;
//计算_totalCount
_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); // 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);
}
}
}

增/改缓存

- (void)setObject:(id)object forKey:(id)key withCost:(NSUInteger)cost {
// 如果设置的object对象是空的那么就会移除这个对象
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将值传入并插入到双向链表的头部
node = [_YYLinkedMapNode new];
node->_cost = cost;
node->_time = now;
node->_key = key;
node->_value = object;
[_lru insertNodeAtHead:node];
}
//如果缓存双向链表总的空间超过了限制大小调用trimToCost清除缓存
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]; //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);
}

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]; //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);
}

查找缓存

- (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) {
//如果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) {
//如果缓存的存储空间还是超过了限制,就从缓存尾部移除一个暂时添加到holder,避免频繁对内存读写,导致锁被占用,
if (_lru->_totalCost > costLimit) {
_YYLinkedMapNode *node = [_lru removeTailNode];
if (node) [holder addObject:node];
} else {
finish = YES;
}
pthread_mutex_unlock(&_lock);
} else {
usleep(10 * 1000); //10 ms
}
}
//最后将待清除的对象所占用的空间清除。
if (holder.count) {
dispatch_queue_t queue = _lru->_releaseOnMainThread ? dispatch_get_main_queue() : YYMemoryCacheGetReleaseQueue();
dispatch_async(queue, ^{
[holder count]; // release in queue
});
}
}

_trimToCost 中如果有超过限制就会不断从缓存链表队尾移除一个对象,然后先将待移除的对象暂时添加到holder,避免频繁对内存读写,导致锁被占用,直到没有超过限制的时候在对应的队列中将这些对象的空间释放。****_trimToCount_trimToAge**** 和 _trimToCost 代码类似,只不过判断的标准不一样而已,这里就不展开介绍了,有兴趣的同学可以查看对应的代码。

YYDiskCache

磁盘缓存和内存缓存对外的接口大体类似,只有一点不大一致的:

  • inlineThreshold

它多出了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]; // 20KB
}

- (instancetype)initWithPath:(NSString *)path
inlineThreshold:(NSUInteger)threshold {
self = [super init];
if (!self) return nil;

//我们在项目中可能会有多个YYDiskCache,我们会将这些YYDiskCache的实例以path为key,缓存到类型为NSMapTable的_globalInstances中。
YYDiskCache *globalCache = _YYDiskCacheGetGlobal(path);
//如果缓存中有就先用缓存中的,避免重复创建
if (globalCache) return globalCache;

//根据threshold来设定缓存的存储方式:
YYKVStorageType type;
if (threshold == 0) {
type = YYKVStorageTypeFile;
} else if (threshold == NSUIntegerMax) {
type = YYKVStorageTypeSQLite;
} else {
type = YYKVStorageTypeMixed;
}

//新建一个YYKVStorage,所有的磁盘存储都由YYKVStorage来完成
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;
//缓存整理主动触发时间默认为1分钟
_autoTrimInterval = 60;

//触发缓存整理
[self _trimRecursively];

//将当前YYDiskCache缓存起来
_YYDiskCacheSetGlobal(self);

//在退出应用的时候会将kv设置为nil
[[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 {
//如果object为空表示从磁盘缓存中删除改对象,这个和YYMemoryCache是一致的
if (!object) {
[self removeObjectForKey:key];
return;
}

//获取某个对象额外需要追加到对象上的数据
NSData *extendedData = [YYDiskCache getExtendedDataFromObject:object];
NSData *value = nil;
//如果指定了自定义的归档方式,那么使用自定义的归档方式来对要缓存的对象归档为NSData存储到磁盘缓存中
if (_customArchiveBlock) {
value = _customArchiveBlock(object);
} else {
//否则使用NSKeyedArchiver进行归档
@try {
value = [NSKeyedArchiver archivedDataWithRootObject:object];
}
@catch (NSException *exception) {
// nothing to do...
}
}
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) {
// nothing to do...
}
}
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) {
//如果filename不为空,表示使用文件作为磁盘缓存,这时候会先将缓存写到文件中
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];
}
}
//由于filename为nil那么会将缓存数据缓存到inline_data字段。
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个字段分别列举如下:

* key               缓存的key
* filename 使用文件缓存的情况下该文件存储的路径
* size 缓存数据大小
* inline_data 数据库缓存的地方
* modification_time 缓存修改时间
* last_access_time 上一次访问时间
* extended_data 额外追加数据

删除对象

- (BOOL)removeItemForKey:(NSString *)key {
if (key.length == 0) return NO;
switch (_type) {
case YYKVStorageTypeSQLite: {
//如果是YYKVStorageTypeSQLite 则只删除数据库记录就可以了,因为这时候数据是存储在数据库记录上的
return [self _dbDeleteItemWithKey:key];
} break;
case YYKVStorageTypeFile:
case YYKVStorageTypeMixed: {
//如果是YYKVStorageTypeFile或者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;
//通过key 从数据库或者到当前缓存对象的缓存记录。
YYKVStorageItem *item = [self _dbGetItemWithKey:key excludeInlineData:NO];
if (item) {
//更新下缓存访问时间
[self _dbUpdateAccessTimeWithKey:key];
//如果缓存记录的filename不为空,则表示是文件缓存
if (item.filename) {
//读取文件,将数据添加到value字段
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/则是和我们电脑的垃圾箱一样。

总结

其实大家看文章开始的那张图就啥都明白了,不明白抽我,抖机~~~~~~

Contents
  1. 1. 开源代码信息