美文网首页
YYImageCache源码浅析

YYImageCache源码浅析

作者: 好有魔力 | 来源:发表于2019-11-04 21:50 被阅读0次

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

    相关文章

      网友评论

          本文标题:YYImageCache源码浅析

          本文链接:https://www.haomeiwen.com/subject/rpyrbctx.html