YYImageCache源码浅析

YYImageCache支持内存和磁盘双缓存,其内部是对YYDiskCacheYYMemoryCache的封装.

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配合使用的.

来看看YYKVStorageYYKVStorageItem都有哪些接口

@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相反,YYMemoryCachekey是引用而不是复制的.API性能类似于NSCache,都是线程安全的.

YYMemoryCacheNSCache的有几处不同:

  • 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
        });
    }
}

你可能感兴趣的:(YYImageCache源码浅析)