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<NSCoding>)objectForKey:(NSString *)key; 
//从缓存中获取某一对象(异步版本)                  
- (void)objectForKey:(NSString *)key withBlock:(void(^)(NSString *key, id<NSCoding> _Nullable object))block;

//向缓存中添加某一对象
- (void)setObject:(nullable id<NSCoding>)object forKey:(NSString *)key;
//向缓存中添加某一对象(异步版本)
- (void)setObject:(nullable id<NSCoding>)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<NSCoding>)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<NSCoding>)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<NSString *> *)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<YYKVStorageItem *> *)getItemForKeys:(NSArray<NSString *> *)keys;
//获取多条缓存数据,但是获取到的每条缓存数据都没有`value`
- (nullable NSArray<YYKVStorageItem *> *)getItemInfoForKeys:(NSArray<NSString *> *)keys;
//获取多条`value`
- (nullable NSDictionary<NSString *, NSData *> *)getItemValueForKeys:(NSArray<NSString *> *)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
        });
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,402评论 6 499
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,377评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,483评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,165评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,176评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,146评论 1 297
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,032评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,896评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,311评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,536评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,696评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,413评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,008评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,659评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,815评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,698评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,592评论 2 353