YYCache是OC用于缓存的第三方框架。
- YYCache:同时实现内存缓存和磁盘缓存,且是线程安全的。
- YYDiskCache:实现磁盘缓存,所有的API是线程安卓的,内部也采用了LRU淘汰算法,主要是SQLite和文件存储两种方式。
- YYKVStorage:实现磁盘缓存,不推荐直接使用此类,该类不是线程安全的。
- YYMemoryCache:实现内存缓存,所有的API是线程安全的,也是采用了LRU淘汰算法来提高性能。
LRU淘汰算法:
LRU(Least recently used,最近最少使用)算法,根据访问的历史记录来对数据进行淘汰。
- 有新数据加入时添加到链表的头部。
- 每当缓存命中(缓存数据被访问),则将数据移动到链表头部。
- 每当链表满的时候,将链表尾部的数据丢弃。
在YYMemoryCache中使用双向链表和NSDictionary实现了LRU淘汰算法。
线程安全-锁:
YYCache使用到了两种锁
- OSSpinLock:自旋锁
- dispatch_semaphore:信号量,当信号量为1时充当锁进行使用
内存缓存使用的是pthread_mutex,由于pthread_mutex相当于do while忙等,等待时会消耗大量的CPU资源。
@interface _YYLinkedMapNode : NSObject {
@package
__unsafe_unretained _YYLinkedMapNode *_prev; // retained by dic,指向上一个节点
__unsafe_unretained _YYLinkedMapNode *_next; // retained by dic,指向下一个节点
id _key;// 缓存的key
id _value;// 缓存的对象
NSUInteger _cost;// 内存消耗
NSTimeInterval _time;// 缓存时间
}
@end
_YYLinkedMapNode:链表的节点
- _prev、_next:分别表示指向上一个节点、下一个节点
- _key:缓存的key
- _value:缓存对象
- _cost:内存消耗
- _time:缓存时间
@interface _YYLinkedMap : NSObject {
@package
CFMutableDictionaryRef _dic; // do not set object directly,用来保存节点
NSUInteger _totalCost;// 总缓存开销
NSUInteger _totalCount;//
_YYLinkedMapNode *_head; // MRU, do not change it directly,头节点
_YYLinkedMapNode *_tail; // LRU, do not change it directly,尾节点
BOOL _releaseOnMainThread;// 是否在主线程释放_YYLinkedMapNode
BOOL _releaseAsynchronously;// 是否异步释放_YYLinkedMapNode
}
_YYLinkedMap:链表
- _dic:用来保存节点
- _totalCost:总缓存开销
- _head、_tail:头节点、尾节点
- _releaseOnMainThread:是否在主线程释放_YYLinkedMapNode
- _releaseAsynchronously:是否异步释放_YYLinkedMapNode
- 插入节点到头部(- (void)insertNodeAtHead:(_YYLinkedMapNode *)node;)
- 将某个节点移动到头部(- (void)bringNodeToHead:(_YYLinkedMapNode *)node;)
- 移除特定节点(- (void)removeNode:(_YYLinkedMapNode *)node;)
- 移除尾部节点(- (_YYLinkedMapNode *)removeTailNode;)
- 移除所有节点(- (void)removeAll;)
移除所有节点源码:
- (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);
}
}
}
通过双向链表来对数据进行操作,以及使用NSDictionary实现了LRU淘汰算法。时间复杂度O(1),5种操作基本是都是对节点的基本操作。
YYMemoryCache
这边介绍两个主要操作:添加缓存,查找缓存
添加缓存
-(void)setObject:(id)object forKey:(id)key withCost:(NSUInteger)cost
{
if(!key)return;
if(!object)
{
[self removeObjectForKey:key];// 缓存对象为nil时,直接移除
return;
}
pthread_mutex_lock(&_lock);// 为了保证数据安全,数据操作前加锁
_YYLinkedMapNode *node = CFDictionaryGetValue(_lru->_dic,(__briddge 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->_totalCount > _countLimit){// 判断当前的缓存进行是否超出了设定值,若超出则进行整理
dispatch_async(_queue,^{
[self trimToCost:_costLimit];
});
}
if(_lru->_totalCost > _costLimit)// 每次添加数据仅有一个,数量上超出时,直接移除尾部那个object
{
_YYLinkedMapNode *node = [_lru removeTailNode];
//线程操作,异步释放
if(_lru->_releaseAsynchronousely)
{
dispatch_queue_t queue = _lru->_releaseOnMainThread ? dispatch_get_main_queue : YYMemoryCacgeGetReleaseQueue();
dispatch_async(queue,^{
[node class];//通过node来对其进行持有,以至于不会在方法调用结束的时候被销毁
});
}else if(_lru->_releaseOnMainThread && !pthread_main_np)
{
dispatch_async(dispatch_get_main_queue(),^{
[node class];//通过node来对其进行持有,以至于不会在方法调用结束的时候被销毁
});
}
}
pthread_mutex_unlock(&_lock);// 操作结束,解锁
}
获取缓存
// 从memory 中取数据时,根据LRU原则,将最新取出的obejct放在栈头
-(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;
}
YYKVStore
该文件主要以两种方式来实现磁盘缓存:SQLite、File,使用两种方式混合进行数据存储主要是为了提高读写效率。写入数据时,SQLite要比文件的方式更快;读取数据的速度取决于文件的大小。据测试,在iphone6中,当文件的大小超过20kb时,File要比SQLite快的多。所以大文件存储时建议采用File的方式,小文件更适合用SQLite.
Save:
- (BOOL)saveItemWithKey:(NSString *)key
value:(NSData *)value
filename:(nullable NSString *)filename
extendedData:(nullable NSData *)extendedData
{
//条件不符合
if(key.length == 0 || value.length == 0)return NO;
if(_type == YYKVStorageTypeFile && filename.length == 0)
{
return NO;
}
if(filename.length)// filename 存在,sqlite 和filename两种方式进行
{
// 用文件进行存储
if(![self _fileWriteWithName:filename data:value])
{
return NO;
}
// 用SQLite进行存储
if(![self _dbSaveWithKey:key value:value filename:filename extendedData:extendedData])
{
// 当使用SQLite存储失败时,删除本地文件存储
[self _fileDeleteWithName:filename];
return NO;
}
return YES;
}else{// filename不存在时,使用sqlite
if(_type != YYKVStorageTypeSDLite)
{
// 根据key删除对应的file文件
NSString *filename = [self _dbGetFilenameWithKey:key];
if(filename)
{
[self _fileDeleteWithName:filename];
}
}
// sqlite 进行存储
return [self _dbSaveWithKey:key value:value filename:nil extendedData:extendedData];
}
}
Remove
-(BOOL)removeItemForkey:(NSString *)key
{
if(key.length == 0) return NO;
switch(_type)
{
case YYKVStorageSQLite:
{
return [self _dbDeleteItemWithKey:key];// 删除SQLite文件
}break;
case YYKVStorageTypeFile:
case YYKVStorageTypeMixed:
{
NSString *filename = [self _dbGetFilenameWithKey:key];// 获取filename
if(filename)
{
[self _dbDeleteWithName:filename];// 删除filename对应的file
}
return [self _dbDeleteItemWithKey:key];// 删除sqlite
}break;
default:return NO;
}
}
Get
- (NSData *)getItemValueForKey:(NSString *)key
{
if(key.length == 0) return nil;
NSData *value = nil;
switch(_type)
{
case YYKVStorageTypeFile:
{
NSString *filename = [self _dbGetFilenameWithKey:key];
if(filename)// 根据filename获取File
{
value = [self _fileReadWithName:filename];
if(!value)// 当value 不存在,用对应的key删除SQLite数据
{
[self _dbDeleteItemWithKey:key];
return nil;
}
}
}break;
case YYKVStorageTypeSQLite:// SQLite方式获取
{
value = [self _dbGetValueWithKey:key];
}break;
case YYKVStorageTypeMixed:
{
NSString *filename = [self _dbGetFilenameWithKey:key];
if(filename)//filename 存在文件获取,不存在SQLite方式获取
{
value = [self _fileReadWithName:filename];
if(!value)
{
[self _dbDeleteItemWithKey:key];
value = nil;
}
}else{
value = [self _dbGetValueWithKey:key];
}
}break;
}
if(value)
{
[self _dbUpdateAccessTimeWithKey:key];//更新文件操作时间
}
return value;
}
File方式主要是用的writeToFile进行存储,SQLite之际是用sqlite3来对文件进行操作。
YYDiskCache
YYDiskCache是对YYKVStorage进行的一次封装,是线程安全的,这边的操作使用的是dispatch_semaphore_signal来确保线程的安全。另外结合LRU淘汰算法,根据文件的大小自动选择存储方式来表达到更好的性能。
- (instancetype)initWithPath:(NSString *)path
inlineThreshold:(NSUInteger)threshold
{
self = [super init];
if(!self) return nil;
//获取缓存的 YYDiskCache
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
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);
//通知 APP即将被杀死时
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_appWillBeTerminated) name:UIApplicationWillTerminateNotification object:nil];
return self;
}