YYImageCache
支持内存和磁盘双缓存,其内部是对YYDiskCache
和YYMemoryCache
的封装.
YYDiskCache
YYDiskCache 是线程安全的键值对缓存,用sqlite支持的键值对和文件系统存储.
- 采用LRU(最近最少使用)算法来淘汰对象.
- 可以使用消耗,对象的创建的时长和缓存对象的数量来控制
- 为每个对象自动判断存储的类型提高性能
先来看看YYDiskCache
提供了那些接口
@interface YYDiskCache: NSObject
//缓存对象的名字,默认nil
@property (nullable, copy) NSString *name;
//缓存的路径
@property (readonly) NSString *path;
//用来决定是存储在数据库还是用文件存储的阈值,默认20KB
@property (readonly) NSUInteger inlineThreshold;
//如果不想使用NSCoding,用它来自定义对象归档过程,默认nil
@property (nullable, copy) NSData *(^customArchiveBlock)(id object);
//如果不想使用NSCoding,用它来自定义对象接档过程,默认nil
@property (nullable, copy) id (^customUnarchiveBlock)(NSData *data);
//缓存数量限制,默认不限制
@property NSUInteger countLimit;
//缓存消耗限制,默认不限制
@property NSUInteger costLimit;
//对象最大到期时间限制,默认不限制
@property NSUInteger costLimit;
//磁盘空余内存限制,默认0即不限制
property NSUInteger freeDiskSpaceLimit;
//触发淘汰的周期,默认1min
@property NSTimeInterval autoTrimInterval;
//用缓存路径初始化
- (nullable instancetype)initWithPath:(NSString *)path;
//用缓存路径和阈值初始化
- (nullable instancetype)initWithPath:(NSString *)path
inlineThreshold:(NSUInteger)threshold
//某一对象是否在缓存中
- (BOOL)containsObjectForKey:(NSString *)key;
//某一对象是否在缓存中(异步版本)
- (void)containsObjectForKey:(NSString *)key withBlock:(void(^)(NSString *key, BOOL contains))block;
//从缓存中获取某一对象
- (nullable id)objectForKey:(NSString *)key;
//从缓存中获取某一对象(异步版本)
- (void)objectForKey:(NSString *)key withBlock:(void(^)(NSString *key, id _Nullable object))block;
//向缓存中添加某一对象
- (void)setObject:(nullable id)object forKey:(NSString *)key;
//向缓存中添加某一对象(异步版本)
- (void)setObject:(nullable id)object forKey:(NSString *)key withBlock:(void(^)(void))block;
//从缓存中移除某一对象
- (void)removeObjectForKey:(NSString *)key;
//从缓存中移除某一对象(异步版本)
- (void)removeObjectForKey:(NSString *)key withBlock:(void(^)(NSString *key))block;
//从缓存中移除所有对象
- (void)removeAllObjects;
//从缓存中移除所有对象(异步版本)
- - (void)removeAllObjectsWithBlock:(void(^)(void))block;
//获取缓存个数
- (NSInteger)totalCount;
//获取缓存个数(异步版本)
- (void)totalCountWithBlock:(void(^)(NSInteger totalCount))block;
//将缓存清除到指定的个数
- (void)trimToCount:(NSUInteger)count;
//将缓存清除到指定的个数(异步版本)
- (void)trimToCount:(NSUInteger)count withBlock:(void(^)(void))block;
//将缓存清除到指定的大小
- (void)trimToCost:(NSUInteger)cost;
//将缓存清除到指定的大小(异步版本)
- (void)trimToCost:(NSUInteger)cost withBlock:(void(^)(void))block;
//清除所有超过指定期限的缓存
- (void)trimToAge:(NSTimeInterval)age;
//清除所有超过指定期限的缓存(异步版本)
- (void)trimToAge:(NSTimeInterval)age withBlock:(void(^)(void))block;
//在某一个对象存储之前设置一个对象和其一起存储
+ (void)setExtendedData:(nullable NSData *)extendedData toObject:(id)object;
//获取和某一条缓存一起存储的对象
+ (nullable NSData *)getExtendedDataFromObject:(id)object;
@end
可以看到,几乎缓存操作的相关方法都有同步和异步版本,而且方法较多,篇幅所限,从几个方面来解析源码
缓存的添加,获取,清除
- (void)setObject:(id)object forKey:(NSString *)key {
if (!key) return;
//传进来的对象是nil就清除该对象
if (!object) {
[self removeObjectForKey:key];
return;
}
//拿到这个对象的关联存储数据
NSData *extendedData = [YYDiskCache getExtendedDataFromObject:object];
NSData *value = nil;
if (_customArchiveBlock) {
//设置了自定义归档过程,用自定义的归档过程获取NSData
value = _customArchiveBlock(object);
} else {
@try {
//用NSCoding归档
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();
}
- (id)objectForKey:(NSString *)key {
if (!key) return nil;
//加读锁
Lock();
//使用YYKVStorage 获取对象
YYKVStorageItem *item = [_kv getItemForKey:key];
Unlock();
if (!item.value) return nil;
id object = nil;
if (_customUnarchiveBlock) {
//调用自定义解档过程
object = _customUnarchiveBlock(item.value);
} else {
//NSCoding解档
@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)removeObjectForKey:(NSString *)key {
if (!key) return;
//加写锁
Lock();
//使用YYKVStorage 移除缓存对象
[_kv removeItemForKey:key];
Unlock();
}
缓存的淘汰
清除所有超过指定期限的缓存方法
- (void)trimToAge:(NSTimeInterval)age {
//加锁
Lock();
//调用私有清除超过指定期限的缓存方法
[self _trimToAge:age];
Unlock();
}
- (void)_trimToAge:(NSTimeInterval)ageLimit {
//期限小于0直接清空所有
if (ageLimit <= 0) {
[_kv removeAllItems];
return;
}
long timestamp = time(NULL);
//当前时间没有超过期限,什么也不做
if (timestamp <= ageLimit) return;
//过期时间合理性判断
long age = timestamp - ageLimit;
if (age >= INT_MAX) return;
//使用YYKVStorage传递过期时长清除缓存
[_kv removeItemsEarlierThanTime:(int)age];
}
通过上面的源码分析,实现存储与淘汰的逻辑都在YYKVStorage中,YYDiskCache在YYKVStorage之上封装了支持线程安全与异步的接口.
YYKVStorage
YYKVStorage
是基于sqlite和文件存储的键值对存储,并且不支持线程安全.
YYKVStorage
是与YYKVStorageItem
配合使用的.
来看看YYKVStorage
和YYKVStorageItem
都有哪些接口
@interface YYKVOStorageItem:NSObject
//缓存的键值
@property (nonatomic, strong) NSString *key;
//缓存的数据
@property (nonatomic, strong) NSData *value;
//存储时使用的文件名
@property (nullable, nonatomic, strong) NSString *filename;
//数据大小
@property (nonatomic) int size;
//最后一次修改的时间戳
@property (nonatomic) int modTime;
//最新的读取时间戳
@property (nonatomic) int accessTime;
//存储关联数据
@property (nullable, nonatomic, strong) NSData *extendedData;
@end
@interface YYKVStorage:NSObject
//存储路径
@property (nonatomic, readonly) NSString *path;
/*
存储类型:
YYKVStorageTypeFile 文件存储,
YYKVStorageTypeSQLite 数据库存储,
YYKVStorageTypeMixed 混合存储
*/
@property (nonatomic, readonly) YYKVStorageType type;
//是否开启调试日志
@property (nonatomic) BOOL errorLogsEnabled;
//使用文件路径和存储类型初始化
- (nullable instancetype)initWithPath:(NSString *)path type:(YYKVStorageType)type;
//存储或者数据
- (BOOL)saveItem:(YYKVStorageItem *)item;
//存储或者更新数据(数据库),如果存储类型是文件存储会报错
- (BOOL)saveItemWithKey:(NSString *)key value:(NSData *)value;
//存储或者更新数据
- (BOOL)saveItemWithKey:(NSString *)key
value:(NSData *)value
filename:(nullable NSString *)filename
extendedData:(nullable NSData *)extendedData;
//用key删除一条数据
- (BOOL)removeItemForKey:(NSString *)key;
//用删除多条数据
- (BOOL)removeItemForKeys:(NSArray *)keys;
//删除所有缓存数据大小大于size的数据
- (BOOL)removeItemsLargerThanSize:(int)size;
//删除所有获取时间比time早的数据
- (BOOL)removeItemsEarlierThanTime:(int)time;
//使用LRU算法删除数据,直到缓存数据的总大小小于size
- (BOOL)removeItemsToFitSize:(int)maxSize;
//使用LRU算法删除数据,直到缓存数据条数小于count
- (BOOL)removeItemsToFitCount:(int)maxCount;
//删除所有的数据
- (BOOL)removeAllItems;
//删除所有的数据,会通报进度
- (void)removeAllItemsWithProgressBlock:(nullable void(^)(int removedCount, int totalCount))progress
endBlock:(nullable void(^)(BOOL error))end;
//获取一条缓存数据
- (nullable YYKVStorageItem *)getItemForKey:(NSString *)key;
//获取一天缓存数据,但是没有`value`
- (nullable YYKVStorageItem *)getItemInfoForKey:(NSString *)key;
//只获取缓存数据的`value`
- (nullable NSData *)getItemValueForKey:(NSString *)key;
//获取多条缓存数据
- (nullable NSArray *)getItemForKeys:(NSArray *)keys;
//获取多条缓存数据,但是获取到的每条缓存数据都没有`value`
- (nullable NSArray *)getItemInfoForKeys:(NSArray *)keys;
//获取多条`value`
- (nullable NSDictionary *)getItemValueForKeys:(NSArray *)keys;
//某条数据是否存在
- (BOOL)itemExistsForKey:(NSString *)key;
//获取缓存数据的条数
- (int)getItemsCount;
//获取缓存数据的大小
- (int)getItemsSize;
@end
插入和更新数据
- (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;
}
//当文件名存在的时候
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 {
//文件名不存在,并且存储类型设置的不是SQLite
if (_type != YYKVStorageTypeSQLite) {
//从数据库中获取文件信息
NSString *filename = [self _dbGetFilenameWithKey:key];
//判断之前是否用文件存储过
if (filename) {
//如果之前存储过则清除之前存储的文件
[self _fileDeleteWithName:filename];
}
}
//调用数据库存储数据
return [self _dbSaveWithKey:key value:value fileName:nil extendedData:extendedData];
}
}
//文件存储方法
- (BOOL)_fileWriteWithName:(NSString *)filename data:(NSData *)data {
//文件路径拼接文件名
NSString *path = [_dataPath stringByAppendingPathComponent:filename];
//写入文件
return [data writeToFile:path atomically:NO];
}
//文件删除方法
- (BOOL)_fileDeleteWithName:(NSString *)filename {
//文件路径拼接文件名
NSString *path = [_dataPath stringByAppendingPathComponent:filename];
//删除文件
return [[NSFileManager defaultManager] removeItemAtPath:path error:NULL];
}
//数据库存储方法
- (BOOL)_dbSaveWithKey:(NSString *)key value:(NSData *)value fileName:(NSString *)fileName extendedData:(NSData *)extendedData {
//编写sql插入或者更新语句
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上下文 sqlite3_stmt
sqlite3_stmt *stmt = [self _dbPrepareStmt:sql];
if (!stmt) return NO;
//获取时间戳
int timestamp = (int)time(NULL);
//绑定key
sqlite3_bind_text(stmt, 1, key.UTF8String, -1, NULL);
//绑定fileName
sqlite3_bind_text(stmt, 2, fileName.UTF8String, -1, NULL);
//绑定文件大小
sqlite3_bind_int(stmt, 3, (int)value.length);
//fileName长度是0,说明是数据库存储
if (fileName.length == 0) {
//向数据库中存储二进制
sqlite3_bind_blob(stmt, 4, value.bytes, (int)value.length, 0);
} else {
//文件存储则直接存NULL
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);
//执行插入sql
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;
}
获取数据
- (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;
}
//私有用key从数据中获取数据
- (YYKVStorageItem *)_dbGetItemWithKey:(NSString *)key excludeInlineData:(BOOL)excludeInlineData {
//根据是否查询关联数据构造查询sql语句
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 ,并缓存
sqlite3_stmt *stmt = [self _dbPrepareStmt:sql];
if (!stmt) return nil;
//绑定key参数
sqlite3_bind_text(stmt, 1, key.UTF8String, -1, NULL);
YYKVStorageItem *item = nil;
//执行sql语句
int result = sqlite3_step(stmt);
//sql执行成功
if (result == SQLITE_ROW) {
//调用私有获取数据方法从sqlite3_stmt中获取数据
item = [self _dbGetItemFromStmt:stmt excludeInlineData:excludeInlineData];
} else {
//sql执行失败
if (result != SQLITE_DONE) {
if (_errorLogsEnabled) NSLog(@"%s line:%d sqlite query error (%d): %s", __FUNCTION__, __LINE__, result, sqlite3_errmsg(_db));
}
}
return item;
}
//从sqlite3_stmt中获取数据
- (YYKVStorageItem *)_dbGetItemFromStmt:(sqlite3_stmt *)stmt excludeInlineData:(BOOL)excludeInlineData {
int i = 0;
//按照查询sql语句中的列顺序,依次取出数据
//取出key
char *key = (char *)sqlite3_column_text(stmt, i++);
//取出fileName
char *filename = (char *)sqlite3_column_text(stmt, i++);
//取出文件大小
int size = sqlite3_column_int(stmt, i++);
//取出数据
const void *inline_data = excludeInlineData ? NULL : sqlite3_column_blob(stmt, i);
int inline_data_bytes = excludeInlineData ? 0 : sqlite3_column_bytes(stmt, i++);
//取出修改时间
int modification_time = sqlite3_column_int(stmt, i++);
//取出获取时间
int last_access_time = sqlite3_column_int(stmt, i++);
//取出存储关联数据
const void *extended_data = sqlite3_column_blob(stmt, i);
int extended_data_bytes = sqlite3_column_bytes(stmt, i++);
//构造 YYKVStorageItem
YYKVStorageItem *item = [YYKVStorageItem new];
//赋值key
if (key) item.key = [NSString stringWithUTF8String:key];
//赋值fileName
if (filename && *filename != 0) item.filename = [NSString stringWithUTF8String:filename];
//赋值文件大小
item.size = size;
//赋值数据data
if (inline_data_bytes > 0 && inline_data) item.value = [NSData dataWithBytes:inline_data length:inline_data_bytes];
//赋值修改时间
item.modTime = modification_time;
//赋值获取时间
item.accessTime = last_access_time;
//赋值存储关联数据
if (extended_data_bytes > 0 && extended_data) item.extendedData = [NSData dataWithBytes:extended_data length:extended_data_bytes];
return item;
}
//更新数据库中的最新获取时间
- (BOOL)_dbUpdateAccessTimeWithKey:(NSString *)key {
//构造UPDATE sql语句
NSString *sql = @"update manifest set last_access_time = ?1 where key = ?2;";
//构造 sqlite上下文 sqlite3_stmt ,并缓存
sqlite3_stmt *stmt = [self _dbPrepareStmt:sql];
if (!stmt) return NO;
//使用当前时间戳绑定获取时间参数
sqlite3_bind_int(stmt, 1, (int)time(NULL));
//绑定Key参数
sqlite3_bind_text(stmt, 2, key.UTF8String, -1, NULL);
//执行sql语句
int result = sqlite3_step(stmt);
// 解析结果
if (result != SQLITE_DONE) {
if (_errorLogsEnabled) NSLog(@"%s line:%d sqlite update error (%d): %s", __FUNCTION__, __LINE__, result, sqlite3_errmsg(_db));
return NO;
}
return YES;
}
//私有从文件系统中加载文件方法
- (NSData *)_fileReadWithName:(NSString *)filename {
//文件路径下拼接文件名
NSString *path = [_dataPath stringByAppendingPathComponent:filename];
//使用NSData读取
NSData *data = [NSData dataWithContentsOfFile:path];
return data;
}
- (BOOL)_dbDeleteItemWithKey:(NSString *)key {
//构造删除 sql语句
NSString *sql = @"delete from manifest where key = ?1;";
//使用sql语句创建sql 上下文环境
sqlite3_stmt *stmt = [self _dbPrepareStmt:sql];
if (!stmt) return NO;
//绑定key值
sqlite3_bind_text(stmt, 1, key.UTF8String, -1, NULL);
//执行sql语句
int result = sqlite3_step(stmt);
//解析执行结果
if (result != SQLITE_DONE) {
if (_errorLogsEnabled) NSLog(@"%s line:%d db delete error (%d): %s", __FUNCTION__, __LINE__, result, sqlite3_errmsg(_db));
return NO;
}
return YES;
}
淘汰数据
//淘汰数据到指定的条数
- (BOOL)removeItemsToFitCount:(int)maxCount {
//如果不限制直接返回成功
if (maxCount == INT_MAX) return YES;
//小于0清除所有
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;
//LRU
//按数据的获取时间升序排序数据,并取出头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;
}
//排序查询数据
- (NSMutableArray *)_dbGetItemSizeInfoOrderByTimeAscWithLimit:(int)count {
//排序sql
NSString *sql = @"select key, filename, size from manifest order by last_access_time asc limit ?1;";
//构造 sqlite3_stmt
sqlite3_st mt *stmt = [self _dbPrepareStmt:sql];
if (!stmt) return nil;
//绑定limit
sqlite3_bind_int(stmt, 1, count);
NSMutableArray *items = [NSMutableArray new];
//循环取出数据并给相应属性赋值
do {
//执行sql
int result = sqlite3_step(stmt);
//成功获取到一条数据
if (result == SQLITE_ROW) {
char *key = (char *)sqlite3_column_text(stmt, 0);
char *filename = (char *)sqlite3_column_text(stmt, 1);
int size = sqlite3_column_int(stmt, 2);
NSString *keyStr = key ? [NSString stringWithUTF8String:key] : nil;
if (keyStr) {
YYKVStorageItem *item = [YYKVStorageItem new];
item.key = key ? [NSString stringWithUTF8String:key] : nil;
item.filename = filename ? [NSString stringWithUTF8String:filename] : nil;
item.size = size;
[items addObject:item];
}
} else if (result == SQLITE_DONE) {
//获取数据完成
break;
} else {
//获取数据失败
if (_errorLogsEnabled) NSLog(@"%s line:%d sqlite query error (%d): %s", __FUNCTION__, __LINE__, result, sqlite3_errmsg(_db));
items = nil;
break;
}
} while (1);
return items;
}
YYMemoryCache
YYMemoryCache
是存储键值对的快速内存缓存,与NSDictionary
相反,YYMemoryCache
的key
是引用而不是复制的.API
性能类似于NSCache
,都是线程安全的.
YYMemoryCache
与NSCache
的有几处不同:
-
YYMemoryCache
使用LRU算法淘汰对象,NSCache
淘汰算法不明确. -
YYMemoryCache
可以由内存占用、缓存数量和最大过期实现来控制;NSCache
的限制是不明确的. -
YYMemoryCache
在收到内存警告和进入后台时自动淘汰对象
YYMemoryCache
的接口:
@interface YYMemoryCache: NSObject
//缓存的名称
@property (nullable, copy) NSString *name;
//缓存数据的条数
@property (readonly) NSUInteger totalCount;
//缓存的内存占用
@property (readonly) NSUInteger totalCost;
//缓存条数限制,默认不限制
@property NSUInteger countLimit;
//缓存大小限制,默认不限制
@property NSUInteger costLimit;
//最大缓存期限限制,默认不限制
@property NSTimeInterval ageLimit;
//自动触发魂村淘汰的时间间隔,默认5秒
@property NSTimeInterval autoTrimInterval;
//在收到内存警告后是否移除所有缓存对象,默认YES
@property BOOL shouldRemoveAllObjectsOnMemoryWarning;
//在程序进入后台后是否移除所有缓存对象,默认YES
@property BOOL shouldRemoveAllObjectsWhenEnteringBackground;
//收到内存警告的回调,默认nil
@property (nullable, copy) void(^didReceiveMemoryWarningBlock)(YYMemoryCache *cache);
//进入后台的回调,默认nil
@property (nullable, copy) void(^didEnterBackgroundBlock)(YYMemoryCache *cache);
//在主线程释放缓存键值对,默认NO
@property BOOL releaseOnMainThread;
//是否为异步释放,默认YES
@property BOOL releaseAsynchronously;
//某一对象是否在缓存中
- (BOOL)containsObjectForKey:(id)key;
//获取缓存对象
- (nullable id)objectForKey:(id)key;
//用指定key设置缓存对象, 0 消耗
- (void)setObject:(nullable id)object forKey:(id)key;
//用指定key设置缓存对象并指定其消耗
- (void)setObject:(nullable id)object forKey:(id)key withCost:(NSUInteger)cost;
//用key清除缓存对象
- (void)removeObjectForKey:(id)key;
//清除所有缓存对象
- (void)removeAllObjects;
//使用LRU淘汰缓存对象直到小于指定的条数
- (void)trimToCount:(NSUInteger)count;
//使用LRU淘汰缓存对象直到小于指定的消耗
- (void)trimToCost:(NSUInteger)cost;
//使用LRU淘汰某一时间点之前的缓存对象
- (void)trimToAge:(NSTimeInterval)age;
@end
缓存的添加
- (void)setObject:(id)object forKey:(id)key withCost:(NSUInteger)cost {
//参数合理性判断
if (!key) return;
//若object为nil 直接清除key对应的缓存对象
if (!object) {
[self removeObjectForKey:key];
return;
}
//加锁,保证线程安全
pthread_mutex_lock(&_lock);
//从_lru CFDictionary获取存储信息节点节点
_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) {
//根据是否在主线程释放,获取queue
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);
}
_lru
是_YYLinkedMap
类型的对象,_YYLinkedMap
内部使用CFDictionary
来快速访问节点,使用双向链表来对数据进行排序(最近获取的对象在表头,获取时间越久远的越靠后).
相关定义
@interface _YYLinkedMap : NSObject {
@package
//用来快速访问节点的 CFDictionary
CFMutableDictionaryRef _dic;
//总消耗
NSUInteger _totalCost;
//总条数
NSUInteger _totalCount;
// 头结点
_YYLinkedMapNode *_head; // MRU, do not change it directly
//尾结点
_YYLinkedMapNode *_tail; // LRU, do not change it directly
//是否在主线程释放
BOOL _releaseOnMainThread;
//是否是异步释放
BOOL _releaseAsynchronously;
}
@interface _YYLinkedMapNode : NSObject {
@package
//前驱节点指针
__unsafe_unretained _YYLinkedMapNode *_prev;
//后继节点指针
__unsafe_unretained _YYLinkedMapNode *_next;
//键值
id _key;
//缓存对象
id _value;
//节点的消耗
NSUInteger _cost;
//最新的访问时间戳
NSTimeInterval _time;
}
缓存的淘汰
以- (void)trimToCount:(NSUInteger)count
方法为例
- (void)trimToCount:(NSUInteger)count {
if (count == 0) {
//count 为0,直接清除所有缓存对象
[self removeAllObjects];
return;
}
//调用私有方法
[self _trimToCount:count];
}
- (void)_trimToCount:(NSUInteger)countLimit {
BOOL finish = NO;
//加锁
pthread_mutex_lock(&_lock);
if (countLimit == 0) {
//限制为0,直接清除所有
[_lru removeAll];
finish = YES;
} else if (_lru->_totalCount <= countLimit) {
//缓存条数没有超过限制直接返回
finish = YES;
}
//解锁
pthread_mutex_unlock(&_lock);
if (finish) return;
NSMutableArray *holder = [NSMutableArray new];
while (!finish) {
//如果加锁成功
if (pthread_mutex_trylock(&_lock) == 0) {
//当缓存条数大于限制
if (_lru->_totalCount > countLimit) {
//从双向链表尾部一个一个的移除
_YYLinkedMapNode *node = [_lru removeTailNode];
//将移除的节点添加到 holder数组中
if (node) [holder addObject:node];
} else {
//当缓存条数小于限制,标记结束退出下一轮循环
finish = YES;
}
pthread_mutex_unlock(&_lock);
} else {
//加锁失败则阻塞10ms
usleep(10 * 1000);
}
}
//如果有要移除的缓存
if (holder.count) {
//根据是否在主线程释放获取相应队列
dispatch_queue_t queue = _lru->_releaseOnMainThread ? dispatch_get_main_queue() : YYMemoryCacheGetReleaseQueue();
dispatch_async(queue, ^{
//在队列中释放holder数组,进而释放holder中的缓存对象
[holder count]; // release in queue
});
}
}