之前分析过NSCache,现在就来看看YYCache是怎么做缓存的。先看个例子:
Person *person = [[Person alloc]init];
person.name = @"你的名字";
YYCache *cache = [[YYCache alloc]initWithName:@"PersonCache"];//创建数据库等
[cache setObject: person forKey:@"key"];
-
YYCache
初始化就是创建数据库等一些准备工作,我们重点看[setObject:forKey:]
:
@implementation YYCache
...
- (instancetype)initWithName:(NSString *)name {
if (name.length == 0) return nil;
NSString *cacheFolder = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) firstObject];
NSString *path = [cacheFolder stringByAppendingPathComponent:name];
return [self initWithPath:path];
}
...
- (void)setObject:(id<NSCoding>)object forKey:(NSString *)key {
[_memoryCache setObject:object forKey:key];//内存
[_diskCache setObject:object forKey:key];//磁盘
}
- 先来看
YYMemoryCache
:
@implementation YYMemoryCache
...
- (void)setObject:(id)object forKey:(id)key {
[self setObject:object forKey:key withCost:0];
}
@implementation YYMemoryCache
...
- (void)setObject:(id)object forKey:(id)key withCost:(NSUInteger)cost {
...
pthread_mutex_lock(&_lock);//安全锁
_YYLinkedMapNode *node = CFDictionaryGetValue(_lru->_dic, (__bridge const void *)(key));
NSTimeInterval now = CACurrentMediaTime();
//双向链表节点保存
if (node) {
...
[_lru bringNodeToHead:node];//放到最前面
} else {
node = [_YYLinkedMapNode new];
...
[_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]; //hold and release in queue 利用子线程释放
});
} else if (_lru->_releaseOnMainThread && !pthread_main_np()) { ... }
}
pthread_mutex_unlock(&_lock);
}
缓存结构跟NSCache
一样也是双向链表节点结构:
@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
@implementation _YYLinkedMap
...
- (void)insertNodeAtHead:(_YYLinkedMapNode *)node {
CFDictionarySetValue(_dic, (__bridge const void *)(node->_key), (__bridge const void *)(node));
_totalCost += node->_cost;
_totalCount++;
if (_head) {
//放到当前head前面
node->_next = _head;
_head->_prev = node;
_head = node;
} else {
_head = _tail = node;//没有head代表只有一个
}
}
- (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;
}
//放到当前head前面
node->_next = _head;
node->_prev = nil;
_head->_prev = node;
_head = node;
}
- 然后先根据
_costLimit
释放溢出缓存,并利用子线程进行释放(详细释放原理可看YYImage源码分析的补充):
@implementation YYMemoryCache
...
- (_YYLinkedMapNode *)removeTailNode {
...
CFDictionaryRemoveValue(_dic, (__bridge const void *)(_tail->_key));//移除最后一个
_totalCost -= _tail->_cost;
_totalCount--;
...
return tail;//返回
}
...
- (void)_trimToCost:(NSUInteger)costLimit {
...
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 { ... }
}
if (holder.count) {
dispatch_queue_t queue = _lru->_releaseOnMainThread ? dispatch_get_main_queue() : YYMemoryCacheGetReleaseQueue();
dispatch_async(queue, ^{
[holder count]; // release in queue 利用子线程释放
});
}
}
...
- (void)trimToCost:(NSUInteger)cost {
[self _trimToCost:cost];
}
-
然后继续第2步,根据
_countLimit
释放多出缓存对象。 -
接着来看
YYDiskCache
:
@implementation YYDiskCache
...
- (void)setObject:(id<NSCoding>)object forKey:(NSString *)key {
...
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) {//判断缓存大小是否大于20KB
filename = [self _filenameForKey:key];//MD5 获取名称
}
}
Lock();
[_kv saveItemWithKey:key value:value filename:filename extendedData:extendedData];
Unlock();
}
如果数据大于20KB,就会保存为文件,并通过MD5
命名文件:
@implementation YYDiskCache
...
- (NSString *)_filenameForKey:(NSString *)key {
NSString *filename = nil;
if (_customFileNameBlock) filename = _customFileNameBlock(key);
if (!filename) filename = key.md5String;
return filename;
}
- 然后进行保存:
@implementation YYKVStorage
...
- (BOOL)saveItemWithKey:(NSString *)key value:(NSData *)value filename:(NSString *)filename extendedData:(NSData *)extendedData {
...
//根据filename判断保存方式
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 {
...
return [self _dbSaveWithKey:key value:value fileName:nil extendedData:extendedData];//保存数据库
}
}
- 如果有
filename
就把数据保存为文件:
@implementation YYKVStorage
...
- (BOOL)_fileWriteWithName:(NSString *)filename data:(NSData *)data {
NSString *path = [_dataPath stringByAppendingPathComponent:filename];
return [data writeToFile:path atomically:NO];
}
然后数据库保存其他数据,filename
作为标识和路径把文件关联起来:
@implementation YYKVStorage
...
- (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);";//sqlite保存为YYKVStorageItem
sqlite3_stmt *stmt = [self _dbPrepareStmt:sql];
... //详情略
return YES;
}
如果没有filename
,就直接全部数据保存到数据库。
- 最后我们来看看获取:
@implementation YYCache
...
- (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;
}
- 先从内存中获取:
@implementation YYMemoryCache
...
- (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;
}
- 找不到再从磁盘中获取:
@implementation YYDiskCache
...
- (id<NSCoding>)objectForKey:(NSString *)key {
if (!key) return nil;
Lock();
YYKVStorageItem *item = [_kv getItemForKey:key];//从数据库读取
...
if (_customUnarchiveBlock) {
object = _customUnarchiveBlock(item.value);//自定义解档
} else {
@try {
object = [NSKeyedUnarchiver unarchiveObjectWithData:item.value];//解档
} @catch (NSException *exception) { ... }
}
...
return object;
}
@implementation YYKVStorage
...
- (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) {//如果有filename关联
item.value = [self _fileReadWithName:item.filename];//读取文件
...
}
}
return item;
}
@implementation YYKVStorage
...
- (YYKVStorageItem *)_dbGetItemWithKey:(NSString *)key excludeInlineData:(BOOL)excludeInlineData {
NSString *sql = excludeInlineData ? @"select key, filename, size, modification_time, last_access_time, extended_data from manifest where key = ?1;" : @"select key, filename, size, inline_data, modification_time, last_access_time, extended_data from manifest where key = ?1;";//sqlite查找
sqlite3_stmt *stmt = [self _dbPrepareStmt:sql];
... //详情略
return item;
}
- 如果有
filename
,就去读取文件数据,接着回到第10步解档:
@implementation YYKVStorage
...
- (NSData *)_fileReadWithName:(NSString *)filename {
NSString *path = [_dataPath stringByAppendingPathComponent:filename];
NSData *data = [NSData dataWithContentsOfFile:path];
return data;
}
- 最终从磁盘获取后,便保存到缓存中。
YYCache
的缓存策略除了有cost
缓存大小、count
缓存数量,还有age
访问时间。因为缓存对象添加时会放在最前面,淘汰缓存时淘汰最后一个,所以说释放缓存时,会优先淘汰最近最少使用的对象。
网友评论