前言
YYCache是一款非常优秀的缓存框架,完全可以替代系统NSCache使用,且还拥有本地缓存功能和自动清理功能,可以说能让你爱不释手
YYCache的作者 ibireme,其对YYCache与其他缓存组件的对比图
YYCache简介
其整体架构图如下所示:
YYCache:通过同时调度YYMemoryCache和YYDiskCache来实现,快速存取,使用YMemoryCache找到指定对象时,如果没找到,通过YYDiskCache到磁盘查找,找到并加入到YMemoryCache中去
YYMemoryCache:快速缓存管理对象,通过字典和双向链表来保证读取和增删的快速性,并及时通过LRU算法清理掉最久未使用的快速缓存,清理间隔默认5s
_YYLinkedMap、_YYLinkedMapNode:其为双向列表对象和,双向列表节点对象,在YYMemoryCache中使用,以保证删减过程能达到 O(n),配合字典的读取,效率有了极大提高
YYDiskCache:磁盘缓存管理对象,通过LRU算法调用YYKVStorage等,清理掉最久未使用的缓存,清理间隔默认一分钟
YYKVStorage、YYKVStorageItem: YYKVStorage主要负责Sqlite储存封装,YYKVStorageItem保存了储存所需要的信息
YYCache源码分析
YYCache主要负责调度YYMemoryCache和YYDiskCache,来实现内存本地双通道存储功能,功能相对简单
更新和删除时,通过YYMemoryCache和YYDiskCache同时更新或者删除,读取指定信息时,先通过YYMemoryCache从内存中查找,找不到时通过YYDiskCache到磁盘查找,找到后并更新YYMemoryCache中的快速缓存
主要代码如下所示:
//通过key获取信息
- (void)objectForKey:(NSString *)key
withBlock:(void (^)(NSString *key, id<NSCoding> object))block {
if (!block) return;
//通过内容查找指定key的信息
id<NSCoding> object = [_memoryCache objectForKey:key];
if (object) {
//找到直接block在子线程返回内容,这里我想是为了保持和本地磁盘找后均在子线程返回,避免出现隐形错误
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
block(key, object);
});
} else {
//内存中没找到,到磁盘中查找,找到后并更新快速缓存
[_diskCache objectForKey:key withBlock:^(NSString *key, id<NSCoding> object) {
if (object && ![_memoryCache objectForKey:key]) {
[_memoryCache setObject:object forKey:key];
}
block(key, object);
}];
}
}
//通过key更新内存和磁盘信息
- (void)setObject:(id<NSCoding>)object forKey:(NSString *)key {
[_memoryCache setObject:object forKey:key];
[_diskCache setObject:object forKey:key];
}
//通过key删除内存和磁盘信息
- (void)removeObjectForKey:(NSString *)key {
[_memoryCache removeObjectForKey:key];
[_diskCache removeObjectForKey:key];
}
复制代码
YYMemoryCache源码分析
YYMemoryCache是一个自动清理的内存缓存工具,其功能只负责快速缓存对象的处理,平时也可以完全替代NSCache使用,而不是使用YYCache框架就必须使用YYCache对象
其主要功能模块为:缓存功能模块、自动清理模块
双向链表_YYLinkedMap
_YYLinkedMap为一个双向链表,其节点为_YYLinkedMapNode,每一个_YYLinkedMapNode都有前驱和后继
结构如下所示:
@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
复制代码
双向链表特点:增删比顺序表或者其他综合的哈希表都快,且在此缓存规则下,增删只需要一步,即更快
缓存功能模块
缓存功能是一个_YYLinkedMap双向链表和字典(CFMutableDictionaryRef)共同储存的一个模块,其中字典通过key-value的方式储存的是双向链表的一个个节点,这样可以保证读取时的快速,而_YYLinkedMap充分利用双向链表的快速增删特性,并且加入和删除操作总是在两端开始操作,因此链表增删操作只需要键值一步操作即可完成,因此大大提高了使用效率
注意:双向列表可以理解为一个特殊的队列
添加对象
添加对象过程需要同时维护好字典和双向链表的关系,且需要加锁来保证线程安全,及时使用者非常小心,也无法保证后台自动清理的安全性问题,因此这里必须加上互斥锁
互斥锁的使用采用了目前效率较高的pthread_mutex_lock锁,其效率和GCD信号量基本持平,且GCD信号量在等待时性能相对其较弱,平时略强,从快速缓存角度,我感觉两者实际用起来都差不多,加解锁频率高的可以优先使用pthread_mutex_lock
- (void)setObject:(id)object forKey:(id)key withCost:(NSUInteger)cost {
if (!key) return;
if (!object) {
[self removeObjectForKey:key];
return;
}
//加锁以保证操作安全
pthread_mutex_lock(&_lock);
//从字典中获取node节点,方便双向列表直接增删操作
_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 {
//当前节点不存在,创建并插入到队首,插入过程中,会set到字典当中,并且会增加总大小和总数量
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在子线程中释放,减少主线程开销
[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);
}
//插入对象的时候,除了快速将其放到队首,还增加了总数量和总大小
- (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;
}
}
复制代码
查找对象
查找对象功能相对简单,通过map查找,并且将刚找到的内容放到链表的最前面,以更新为最新访问
- (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;
}
复制代码
自动清理功能
自动清理功能调用方法为_trimRecursively,在该对象初始化时便延时调用,且注册了内存警告和切入后台操作,内存警告和切入后台,马上清理掉内容中的所有内容,以保证应用内存减少,增加应用后台存货率
这里会通过数量、大小、时间三个维度来清理缓存,其中默认都是最大值,即默认不清理,且由于逻辑相似这里只介绍一个处理方法
//延迟清理内存
- (void)_trimRecursively {
__weak typeof(self) _self = self;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(_autoTrimInterval*NSEC_PER_SEC)),
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
__strong typeof(_self) self = _self;
if (!self) return;
//后台清理内存,当self释放时,该方法不在调用,因此通过GCD的延时方法能减少功能的维护
[self _trimInBackground];
[self _trimRecursively]; //递归调用延时清理方法
});
}
//子线程中通过数量、大小、时间三个维度来清理缓存,其中默认都是最大值,即默认不清理
- (void)_trimInBackground {
dispatch_async(_queue, ^{
[self _trimToCost:self->_costLimit];
[self _trimToCount:self->_countLimit];
[self _trimToAge:self->_ageLimit];
});
}
//通过大小限制清理缓存(其他的两个都一样,这里就不多介绍了
- (void)_trimToCost:(NSUInteger)costLimit {
BOOL finish = NO;
pthread_mutex_lock(&_lock);
//如果最大限制为0,则直接清理全部
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) {
//如果互斥锁已经锁定,则返回不为1的参数,当前已经加锁一次,还可以使用如果外部有方法调用,
//则此时信号量低于0,尝试加锁会失败且返回不为0的数,因此此操作是为了避免外部在读写时删减
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 {
//间隔10ms处理,避免死锁
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
});
}
}
复制代码
YYDiskCache源码分析
YYDiskCache负责磁盘缓存的处理,包括调用YYKVStorage进行本地缓存的写入和读取,YYDiskCache主要负责缓存方法的读取功能以及根据缓存设置大小清理缓存功能,模式与YYMemoryCache很相似
_globalInstances
_globalInstances是保存多个YYDiskCache对象,用于区分不同路径下的对象保存,避免缓存出现问题
static NSMapTable *_globalInstances;
static dispatch_semaphore_t _globalInstancesLock;
//初始化globalInstances对象为NSMapTable类型,方便管理,当某个对象不使用时释放以减少内存
//另初始化信号量作为互斥锁使用
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];
});
}
//获取根据path获取YYDiskCache对象
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;
}
//初始化时根据key设置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);
}
复制代码
YYDiskCache初始化
YYDiskCache初始化的时候除了使用globalCache之外,还根据threshold区分了储存的类型,有三种:根据文件名以普通文件方式储存、保存到数据库、混合模式(小数据写入到数据库,大文件直接保存单个文件)
还初始化了YYKVStorage类,其主要是负责磁盘相关操作,包括数据库的读写磁盘操作以及普通文件的写入磁盘操作
与快速缓存一样,也开启了定时清理磁盘功能,间隔默认一分钟
- (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;
}
复制代码
读写缓存操作
写入操作会对对象先进行归档操作,然后根据类型判断怎么写入到文件中去
- (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) {
// nothing to do...
}
}
if (!value) return;
NSString *filename = nil;
//对非数据库操作类型进行处理
if (_kv.type != YYKVStorageTypeSQLite) {
if (value.length > _inlineThreshold) {
filename = [self _filenameForKey:key];
}
}
//通过YYKVStorage写入到本地文件
Lock();
[_kv saveItemWithKey:key value:value filename:filename extendedData:extendedData];
Unlock();
}
复制代码
读取操作为直接到本地获取,获取的同时并更新本地文件时间信息
- (id<NSCoding>)objectForKey:(NSString *)key {
if (!key) return nil;
Lock();
//读取本地的YYKVStorageItem类型,里面包含了Value的地址信息
//如果有fileName则缓存数据Value,则单独保存文件到目录,否则就是归档数据保存在item的value中
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;
}
复制代码
自动清理操作
系统在初始化的时候就开启了自动清理工作,即调用一个递归的方法_trimRecursively,间隔一分钟进行一次清理
- (void)_trimRecursively {
__weak typeof(self) _self = self;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW,
(int64_t)(_autoTrimInterval * NSEC_PER_SEC)),
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
__strong typeof(_self) self = _self;
if (!self) return;
[self _trimInBackground];
[self _trimRecursively];
});
}
//主要清理方法,分别根据总大小、文件总数量、文件时间进行清理、磁盘空余时间,文件相关部分在YYKVStorage中介绍
- (void)_trimInBackground {
__weak typeof(self) _self = self;
dispatch_async(_queue, ^{
__strong typeof(_self) self = _self;
if (!self) return;
Lock();
[self _trimToCost:self.costLimit];
[self _trimToCount:self.countLimit];
[self _trimToAge:self.ageLimit];
[self _trimToFreeDiskSpace:self.freeDiskSpaceLimit];
Unlock();
});
}
//根据磁盘剩余空间对缓存进行清理
- (void)_trimToFreeDiskSpace:(NSUInteger)targetFreeDiskSpace {
if (targetFreeDiskSpace == 0) return;
//获取内容总大小
int64_t totalBytes = [_kv getItemsSize];
if (totalBytes <= 0) return;
//根据磁盘实际总剩余和预设最大剩余空间来计算要清理的内容大小,然后通过设置上限进行清理
int64_t diskFreeBytes = _YYDiskSpaceFree();
if (diskFreeBytes < 0) return;
int64_t needTrimBytes = targetFreeDiskSpace - diskFreeBytes;
if (needTrimBytes <= 0) return;
int64_t costLimit = totalBytes - needTrimBytes;
if (costLimit < 0) costLimit = 0;
//开始清理掉指定大小的数据
[self _trimToCost:(int)costLimit];
}
复制代码
YYKVStorage源码分析
YYKVStorage在YYDiskCache中被调用,其为对所有磁盘操作的一个文件封装,包括了数据库的初始化,表的创建、内容检索、内容删除、内容大小计算等
YYKVStorageItem
数据库缓存中保存的就是该类的信息,当有filename时,value值会被保存在相应的目录下,否则保存在value字段中
@interface YYKVStorageItem : NSObject
@property (nonatomic, strong) NSString *key; ///< key
@property (nonatomic, strong) NSData *value; ///< value
@property (nullable, nonatomic, strong) NSString *filename; ///< filename (nil if inline)
@property (nonatomic) int size; ///< value's size in bytes
@property (nonatomic) int modTime; ///< modification unix timestamp
@property (nonatomic) int accessTime; ///< last access unix timestamp
///< extended data (nil if no extended data)
@property (nullable, nonatomic, strong) NSData *extendedData;
@end
复制代码
YYKVStorage
这里面主要介绍核心操作,关于数据库的细节操作,可以查看详细代码,照猫画虎相信可以自行设计出合适的内容
YYKVStorage的数据相关操作不是线程安全的,在YYDiskCache中对其进行了安全处理
YYKVStorage的初始化
其初始化过程中除了设置目录之外,还创建了数据库,设置了数据库表格table
- (instancetype)initWithPath:(NSString *)path type:(YYKVStorageType)type {
if (path.length == 0 || path.length > kPathLengthMax) {
NSLog(@"YYKVStorage init error: invalid path: [%@].", path);
return nil;
}
if (type > YYKVStorageTypeMixed) {
NSLog(@"YYKVStorage init error: invalid type: %lu.", (unsigned long)type);
return nil;
}
self = [super init];
_path = path.copy;
_type = type;
_dataPath = [path stringByAppendingPathComponent:kDataDirectoryName];
_trashPath = [path stringByAppendingPathComponent:kTrashDirectoryName];
_trashQueue = dispatch_queue_create("com.ibireme.cache.disk.trash", DISPATCH_QUEUE_SERIAL);
_dbPath = [path stringByAppendingPathComponent:kDBFileName];
_errorLogsEnabled = YES;
NSError *error = nil;
if (![[NSFileManager defaultManager] createDirectoryAtPath:path
withIntermediateDirectories:YES
attributes:nil
error:&error] ||
![[NSFileManager defaultManager] createDirectoryAtPath:
[path stringByAppendingPathComponent:kDataDirectoryName]
withIntermediateDirectories:YES
attributes:nil
error:&error] ||
![[NSFileManager defaultManager] createDirectoryAtPath:
[path stringByAppendingPathComponent:kTrashDirectoryName]
withIntermediateDirectories:YES
attributes:nil
error:&error]) {
NSLog(@"YYKVStorage init error:%@", error);
return nil;
}
//打开数据库并初始化数据库表格
if (![self _dbOpen] || ![self _dbInitialize]) {
// db file may broken...
[self _dbClose];
[self _reset]; // rebuild
if (![self _dbOpen] || ![self _dbInitialize]) {
[self _dbClose];
NSLog(@"YYKVStorage init error: fail to open sqlite db.");
return nil;
}
}
[self _fileEmptyTrashInBackground]; // empty the trash if failed at last time
return self;
}
//打开数据库操作
- (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;
}
}
//初始化数据库表格
- (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];
}
复制代码
保存数据
保存数据是根据保存类型,将数据保存到数据库和单个文件当中
- (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;
}
//item中设置了保存的文件名字,则优先将value保存到单个文件中
if (filename.length) {
//将value保存到文件当中
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];
}
}
复制代码
读取信息
读取信息除了在数据库中读取item的信息还需要读取到value数据,其可能存放在单个文件当中
- (NSData *)getItemValueForKey:(NSString *)key {
if (key.length == 0) return nil;
NSData *value = nil;
switch (_type) {
case YYKVStorageTypeFile: {
//根据key从数据库中查询到其文件名,然后到相应的单个文件目录读取内容
NSString *filename = [self _dbGetFilenameWithKey:key];
if (filename) {
//读取文件内容
value = [self _fileReadWithName:filename];
if (!value) {
//没内容就删除文件
[self _dbDeleteItemWithKey:key];
value = nil;
}
}
} break;
case YYKVStorageTypeSQLite: {
//仅仅存放在数据库中,则直接读取数据库中的value即可
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中取出相应信息
value = [self _dbGetValueWithKey:key];
}
} break;
}
if (value) {
[self _dbUpdateAccessTimeWithKey:key];
}
return value;
}
复制代码
删除数据
删除数据是根据类型该清理数据库的清理数据库,该删除文件的删除文件
- (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];
}
//数据库中删除该item信息
return [self _dbDeleteItemWithKey:key];
} break;
default: 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;
//每次取出16条信息进行删除,并更新总数量,如果总数量没超过限制或者空了,则直接结束
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;
}
复制代码
最后
YYCache的框架逻辑清晰,每个模块相对比较独立,均可以支撑起相应功能,我们可以在设置自己框架的时候参考其思想,来优化自己项目质量
另外YYKit里面还有很多我们要学习的东西,一起来探索提升吧