YYKit-YYCache源码分析

YYKit-YYCache源码分析

YYCache是一个高性能iOS缓存框架,是YYKit组件之一。

框架架构

  • YYCache:该框架最外层接口,内部使用YYMemeoryCache和YYDiskCache进行内存缓存和磁盘缓存

  • YYMemoryCache:内存缓存,使用双向链表的LRU算法的缓存机制

  • YYDiskCache:磁盘缓存,使用文件存储和SQLite数据库存储,支持异步操作

  • YYKVOStorage:YYDiskCache的底层具体实现,用于磁盘缓存

YYCache API详解

  • YYCache的内部使用YYMemeoryCache和YYDiskCache进行内存缓存和磁盘缓存,API基本和NSCache保持一致,所有方法都是线程安全。

    • YYCache的属性和方法

    • @interface YYCache : NSObject
      
      /** 缓存名称 */
      @property (copy, readonly) NSString *name;
      
      /** 内存缓存 对象 */
      @property (strong, readonly) YYMemoryCache *memoryCache;
      
      /** 磁盘缓存 对象 */
      @property (strong, readonly) YYDiskCache *diskCache;
      
      /**
       初始化指定缓存名称的实例对象
       具有相同名称的多个实例对象使缓存不稳定
       */
      - (nullable instancetype)initWithName:(NSString *)name;
      
      /**
       初始化指定缓存路径的实例对象
       */
      - (nullable instancetype)initWithPath:(NSString *)path NS_DESIGNATED_INITIALIZER;
      
      /**
       便利初始化指定缓存名称的实例对象
       */
      + (nullable instancetype)cacheWithName:(NSString *)name;
      
      /**
       便利初始化指定缓存路径的实例对象
       */
      + (nullable instancetype)cacheWithPath:(NSString *)path;
      
      - (instancetype)init UNAVAILABLE_ATTRIBUTE;
      + (instancetype)new UNAVAILABLE_ATTRIBUTE;
      
      /**
       通过指定的key值判断是否存在缓存
       */
      - (BOOL)containsObjectForKey:(NSString *)key;
      
      /**
        通过指定的key值判断是否存在缓存 带有Block回调
       */
      - (void)containsObjectForKey:(NSString *)key withBlock:(nullable void(^)(NSString *key, BOOL contains))block;
      
      /**
       返回指定的key值的缓存
       */
      - (nullable id<NSCoding>)objectForKey:(NSString *)key;
      
      /**
       返回指定的key值的缓存 带有Block回调
       */
      - (void)objectForKey:(NSString *)key withBlock:(nullable void(^)(NSString *key, id<NSCoding> object))block;
      
      /**
       通过指定的key值和object 设置缓存
       */
      - (void)setObject:(nullable id<NSCoding>)object forKey:(NSString *)key;
      
      /**
       通过指定的key值和object 设置缓存 带有Block回调
       */
      - (void)setObject:(nullable id<NSCoding>)object forKey:(NSString *)key withBlock:(nullable void(^)(void))block;
      
      /**
       删除指定的key值的缓存
       */
      - (void)removeObjectForKey:(NSString *)key;
      
      /**
       删除指定的key值的缓存 带有Block回调
       */
      - (void)removeObjectForKey:(NSString *)key withBlock:(nullable void(^)(NSString *key))block;
      
      /**
       删除所有缓存
       */
      - (void)removeAllObjects;
      
      /**
       删除所有缓存 带有Block回调
       */
      - (void)removeAllObjectsWithBlock:(void(^)(void))block;
      
      /**
       删除所有缓存 带有删除进度和完成Block回调
       该方法立即返回并在后台使用block执行clear操作。
       */
      - (void)removeAllObjectsWithProgressBlock:(nullable void(^)(int removedCount, int totalCount))progress
                                       endBlock:(nullable void(^)(BOOL error))end;
      
      @end
      
      
      
  • YYCache的方法实现

  • - (instancetype)initWithPath:(NSString *)path {
        if (path.length == 0) return nil;
        // 初始化磁盘缓存
        YYDiskCache *diskCache = [[YYDiskCache alloc] initWithPath:path];
        if (!diskCache) return nil;
        NSString *name = [path lastPathComponent];
        // 初始化内存缓存
        YYMemoryCache *memoryCache = [YYMemoryCache new];
        memoryCache.name = name;
        
        self = [super init];
        _name = name;
        _diskCache = diskCache;
        _memoryCache = memoryCache;
        return self;
    }
    
    - (BOOL)containsObjectForKey:(NSString *)key {
        // 先判断内存缓存是否存在
        return [_memoryCache containsObjectForKey:key] || [_diskCache containsObjectForKey:key];
    }
    
    - (void)containsObjectForKey:(NSString *)key withBlock:(void (^)(NSString *key, BOOL contains))block {
        if (!block) return;
        
         // 先判断内存缓存是否存在
        if ([_memoryCache containsObjectForKey:key]) {
            // 回到主线程执行Block回调
            dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
                block(key, YES);
            });
        } else  {
            // 不存在再判断磁盘缓存是否存在
            [_diskCache containsObjectForKey:key withBlock:block];
        }
    }
    
    - (id<NSCoding>)objectForKey:(NSString *)key {
        // 获取内存缓存
        id<NSCoding> object = [_memoryCache objectForKey:key];
        if (!object) {
            // 如果内存缓存不存在,再去获取磁盘缓存
            object = [_diskCache objectForKey:key];
            if (object) {
                // 如果磁盘缓存存在,把磁盘缓存存在存入内存缓存中
                [_memoryCache setObject:object forKey:key];
            }
        }
        return object;
    }
    
    - (void)setObject:(id<NSCoding>)object forKey:(NSString *)key {
        // 先写入内存缓存,再写入磁盘缓存
        [_memoryCache setObject:object forKey:key];
        [_diskCache setObject:object forKey:key];
    }
    - (void)removeObjectForKey:(NSString *)key {
        // 先删除内存缓存,再删除磁盘缓存
        [_memoryCache removeObjectForKey:key];
        [_diskCache removeObjectForKey:key];
    }
    

YYMemeryCache

  • 缓存淘汰算法: 缓存支持 LRU (least-recently-used) 淘汰算法
  • 缓存控制: 支持多种缓存控制方法:总数量、总大小、存活时间

YYMemeryCache的缓存淘汰算法

LRU(least-recently-used) 最近最少使用算法

  • 核心思想:如果数据最近被访问过,那么将来被访问的几率也更高
  • 策略:最近使用的数据放在最前面,这样长时间未使用数据在后面,淘汰删除

YYMemeryCache中LRU算法的具体实现:

在YYMemeryCache中LRU采用双向链表的方式实现,把添加或者使用的数据插入到链表最前面,删除数据是从链表最后面删除内存缓存。双向链表通过 _YYLinkedMapNode_YYLinkedMapNode来实现

  • _YYLinkedMapNode: 链表节点 包含缓存数据的具体信息

    @interface _YYLinkedMapNode : NSObject {
        @package
        /** 前一个缓存数据节点 */
        __unsafe_unretained _YYLinkedMapNode *_prev; // retained by dic
         /** 后一个缓存数据节点 */
        __unsafe_unretained _YYLinkedMapNode *_next; // retained by dic
        /** 缓存数据的key */
        id _key;
        /** 缓存数据值 */
        id _value;
        /** 缓存数据花费成本 */
        NSUInteger _cost;
        /** 缓存数据访问时间 */
        NSTimeInterval _time;
    }
    @end
    
  • _YYLinkedMapNode:链表类,用保存和管理所有内存缓存数据节点

    @interface _YYLinkedMap : NSObject {
        @package
        /** 存储缓存数据节点的字典 */
        CFMutableDictionaryRef _dic; // do not set object directly
        /** 缓存数据对象总花费 */
        NSUInteger _totalCost;
        /** 缓存数据对象总数量 */
        NSUInteger _totalCount;
        /** 链表中的头节点 */
        _YYLinkedMapNode *_head; // MRU, do not change it directly
        /** 链表中的尾节点 */
        _YYLinkedMapNode *_tail; // LRU, do not change it directly
        /** 在主线程中释放 */
        BOOL _releaseOnMainThread;
         /** 在子线程中释放 */
        BOOL _releaseAsynchronously;
    }
    
    /** 链表中插入头节点 */
    - (void)insertNodeAtHead:(_YYLinkedMapNode *)node;
    
    /** 把链表中节点转换成头节点 */
    - (void)bringNodeToHead:(_YYLinkedMapNode *)node;
    
    /** 在链表中删除节点 */
    - (void)removeNode:(_YYLinkedMapNode *)node;
    
    /** 如果尾部节点存在,就删除尾部节点 */
    - (_YYLinkedMapNode *)removeTailNode;
    
    /** 在链表中删除所有 */
    - (void)removeAll;
    
    @end
    
  • _YYLinkedMapNode:链表类具体实现

    @implementation _YYLinkedMap
    
    - (instancetype)init {
        self = [super init];
        // 创建空的字典
        _dic = CFDictionaryCreateMutable(CFAllocatorGetDefault(), 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
        // 内存缓存默认释放在子线程
        _releaseOnMainThread = NO;
        _releaseAsynchronously = YES;
        return self;
    }
    
    - (void)dealloc {
        CFRelease(_dic);
    }
    
    // 插入node节点,node设置为头节点
    - (void)insertNodeAtHead:(_YYLinkedMapNode *)node {
        // 内存缓存节点添加到字典中
        CFDictionarySetValue(_dic, (__bridge const void *)(node->_key), (__bridge const void *)(node));
        // 内存缓存总开销增加node的开销
        _totalCost += node->_cost;
         // 内存缓存总数量增加1
        _totalCount++;
        
        if (_head) { // 头节点存在
            // node节点的后一个节点是头节点
            node->_next = _head;
            // 现在的头结点是前一个是node节点
            _head->_prev = node;
            // node节点设置为头节点
            _head = node;
        } else {
           //头节点不存在, 那么尾节点一定不存在,node节点同时设置为头结点和尾节点
            _head = _tail = node;
        }
    }
    
    // 使node节点作为头节点
    - (void)bringNodeToHead:(_YYLinkedMapNode *)node {
        // node 已经是头节点
        if (_head == node) return;
        
        if (_tail == node) { // node是尾节点
            // 使node的前一个节点为尾节点
            _tail = node->_prev;
            // 使node的前一个节点,现在的尾节点的后一个节点为空
            _tail->_next = nil;
        } else { // node不是尾节点 从链表中移除node节点
            // node的前一个节点作为node后一个节点的前一个节点
            node->_next->_prev = node->_prev;
            // node的后一个节点作为node的前一个节点的后一个节点
            node->_prev->_next = node->_next;
        }
        // 头节点作为node节点的后一个节点
        node->_next = _head;
        // node的前一个为空
        node->_prev = nil;
        // nodeh节点作为头节点的前一个节点
        _head->_prev = node;
        // node节点设置为头节点
        _head = node;
    }
    
    // 从双向链表中移除node节点
    - (void)removeNode:(_YYLinkedMapNode *)node {
        // 从字典中移除node节点
        CFDictionaryRemoveValue(_dic, (__bridge const void *)(node->_key));
        // 内存缓存总开销删除node的开销
        _totalCost -= node->_cost;
         // 内存缓存总数量减少1
        _totalCount--;
        if (node->_next) node->_next->_prev = node->_prev;
        if (node->_prev) node->_prev->_next = node->_next;
        if (_head == node) _head = node->_next;
        if (_tail == node) _tail = node->_prev;
    }
    
    // 从双向链表中移除尾节点
    - (_YYLinkedMapNode *)removeTailNode {
        if (!_tail) return nil;
        _YYLinkedMapNode *tail = _tail;
        // 从字典中移除尾节点
        CFDictionaryRemoveValue(_dic, (__bridge const void *)(_tail->_key));
        // 内存缓存总开销删除尾节点的开销
        _totalCost -= _tail->_cost;
        // 内存缓存总数量减少1
        _totalCount--;
        if (_head == _tail) {
            // 头节点相等于尾节点时,头节点和尾节点都为空
            _head = _tail = nil;
        } else { // 头节点相不等于尾节点时
            // 尾节点的前一个节点作为尾节点
            _tail = _tail->_prev;
            // 现在的尾节点的后一个为空
            _tail->_next = nil;
        }
        return tail;
    }
    
    // 删除所有内存缓存
    - (void)removeAll {
        _totalCost = 0;
        _totalCount = 0;
        _head = nil;
        _tail = nil;
        // 字典的内存缓存数量大于0
        if (CFDictionaryGetCount(_dic) > 0) {
            CFMutableDictionaryRef holder = _dic;
            // 创建一个空的字典赋值给_dic
            _dic = CFDictionaryCreateMutable(CFAllocatorGetDefault(), 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
            
            // holder 在指定的线程中释放
            if (_releaseAsynchronously) {
                dispatch_queue_t queue = _releaseOnMainThread ? dispatch_get_main_queue() : YYMemoryCacheGetReleaseQueue();
                dispatch_async(queue, ^{
                    CFRelease(holder); // hold and release in specified queue
                });
            } else if (_releaseOnMainThread && !pthread_main_np()) {
                dispatch_async(dispatch_get_main_queue(), ^{
                    CFRelease(holder); // hold and release in specified queue
                });
            } else {
                CFRelease(holder);
            }
        }
    }
    
    @end
    

YYMemeryCache缓存清理策略:

  • init方法

    - (instancetype)init {
        self = super.init;
        pthread_mutex_init(&_lock, NULL);
        _lru = [_YYLinkedMap new];
        _queue = dispatch_queue_create("com.ibireme.cache.memory", DISPATCH_QUEUE_SERIAL);
        
        _countLimit = NSUIntegerMax;
        _costLimit = NSUIntegerMax;
        _ageLimit = DBL_MAX;
        _autoTrimInterval = 5.0;
        // 当收到内存警告时,是否删除所有内存缓存
        _shouldRemoveAllObjectsOnMemoryWarning = YES;
          // 当程序退出到后台时, 是否删除所有内存缓存
        _shouldRemoveAllObjectsWhenEnteringBackground = YES;
        
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_appDidReceiveMemoryWarningNotification) name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_appDidEnterBackgroundNotification) name:UIApplicationDidEnterBackgroundNotification object:nil];
        
        [self _trimRecursively];
        return self;
    }
    
  • _trimRecursively :递归清理内存缓存方法

    - (void)_trimRecursively {
        __weak typeof(self) _self = self;
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(_autoTrimInterval * NSEC_PER_SEC)), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
            __strong typeof(_self) self = _self;
            if (!self) return;
            [self _trimInBackground];
            [self _trimRecursively];
        });
    }
    
    - (void)_trimInBackground {
        dispatch_async(_queue, ^{
              // 删除内存缓存到指定开销
            [self _trimToCost:self->_costLimit];
            // 删除内存缓存到指定数量
            [self _trimToCount:self->_countLimit];
            // 删除内存缓存指定时间之前的缓存
            [self _trimToAge:self->_ageLimit];
        });
    }
    
    

    从上面的代码可以看出YYMemeryCache在init方法调用_trimRecursively,这个方法从它的具体实现,可以看出是递归调用清理内存缓存,从总大小、总数量、访问时间来控制缓存。

YYMemeryCache:监听UIApplicationDidReceiveMemoryWarningNotificationUIApplicationDidEnterBackgroundNotification两个通知,当收到通知时,删除所有内存缓存。

  • - (void)_appDidReceiveMemoryWarningNotification {
        if (self.didReceiveMemoryWarningBlock) {
            self.didReceiveMemoryWarningBlock(self);
        }
        if (self.shouldRemoveAllObjectsOnMemoryWarning) {
            [self removeAllObjects];
        }
    }
    
    - (void)_appDidEnterBackgroundNotification {
        if (self.didEnterBackgroundBlock) {
            self.didEnterBackgroundBlock(self);
        }
        if (self.shouldRemoveAllObjectsWhenEnteringBackground) {
            [self removeAllObjects];
        }
    }
    
  • YYMemeryCache线程安全

    • 在YYMemeryCache中对内存缓存中添加、删除、查询的操作是都是使用pthread_mutex 互斥锁来使线程同步,来保证所有方法都是线程安全。

      - (NSUInteger)totalCount {
          pthread_mutex_lock(&_lock);
          NSUInteger count = _lru->_totalCount;
          pthread_mutex_unlock(&_lock);
          return count;
      }
      
      - (NSUInteger)totalCost {
          pthread_mutex_lock(&_lock);
          NSUInteger totalCost = _lru->_totalCost;
          pthread_mutex_unlock(&_lock);
          return totalCost;
      }
      
      - (BOOL)releaseOnMainThread {
          pthread_mutex_lock(&_lock);
          BOOL releaseOnMainThread = _lru->_releaseOnMainThread;
          pthread_mutex_unlock(&_lock);
          return releaseOnMainThread;
      }
      
      - (void)setReleaseOnMainThread:(BOOL)releaseOnMainThread {
          pthread_mutex_lock(&_lock);
          _lru->_releaseOnMainThread = releaseOnMainThread;
          pthread_mutex_unlock(&_lock);
      }
      
      - (BOOL)releaseAsynchronously {
          pthread_mutex_lock(&_lock);
          BOOL releaseAsynchronously = _lru->_releaseAsynchronously;
          pthread_mutex_unlock(&_lock);
          return releaseAsynchronously;
      }
      
      - (void)setReleaseAsynchronously:(BOOL)releaseAsynchronously {
          pthread_mutex_lock(&_lock);
          _lru->_releaseAsynchronously = releaseAsynchronously;
          pthread_mutex_unlock(&_lock);
      }
      
      - (BOOL)containsObjectForKey:(id)key {
          if (!key) return NO;
          pthread_mutex_lock(&_lock);
          BOOL contains = CFDictionaryContainsKey(_lru->_dic, (__bridge const void *)(key));
          pthread_mutex_unlock(&_lock);
          return contains;
      }
      
      - (id)objectForKey:(id)key {
          if (!key) return nil;
          pthread_mutex_lock(&_lock);
          ......
          pthread_mutex_unlock(&_lock);
          return node ? node->_value : nil;
      }
      
      - (void)setObject:(id)object forKey:(id)key withCost:(NSUInteger)cost {
          if (!key) return;
          if (!object) {
              [self removeObjectForKey:key];
              return;
          }
          pthread_mutex_lock(&_lock);
          ......
          pthread_mutex_unlock(&_lock);
      }
      
      - (void)removeObjectForKey:(id)key {
          if (!key) return;
          pthread_mutex_lock(&_lock);
          ......
          pthread_mutex_unlock(&_lock);
      }
      
      - (void)removeAllObjects {
          pthread_mutex_lock(&_lock);
          [_lru removeAll];
          pthread_mutex_unlock(&_lock);
      }
      
      

YYDiskCache

YYDiskCache是一个线程安全的磁盘缓存,它存储由SQLite支持的键值对和文件系统存储(类似于NSURLCache的磁盘缓存。

  • 缓存淘汰算法: 缓存支持 LRU (least-recently-used) 淘汰算法
  • 缓存控制: 支持多种缓存控制方法:总数量、总大小、存活时间
  • 可以配置磁盘空间没有空闲时自动删除缓存数据
  • 根据缓存数据对象大小来自动获取对象的存储类型

YYDiskCahce存储的具体实现是通过YYKVStroage来实现,在YYKVStorage有个枚举YYKVStorageType,这个是设置存储类型

typedef NS_ENUM(NSUInteger, YYKVStorageType) {
    
    /// 缓存数据value仅仅存储在文件系统
    YYKVStorageTypeFile = 0,
    
    /// 缓存数据value仅仅存储在sqlite中
    YYKVStorageTypeSQLite = 1,
    
    /// 根据缓存数据value大小存储在文件系统并且存储在sqlite中
    YYKVStorageTypeMixed = 2,
};

接下来,看下YYDiskCahce的初始化的方法,我们可以在里面看见存储类型的设置和其他方法的调用

- (instancetype)initWithPath:(NSString *)path {
    return [self initWithPath:path inlineThreshold:1024 * 20]; // 20KB
}

- (instancetype)initWithPath:(NSString *)path
             inlineThreshold:(NSUInteger)threshold {
    self = [super init];
    if (!self) return nil;
    
    YYDiskCache *globalCache = _YYDiskCacheGetGlobal(path);
    if (globalCache) return globalCache;
    
    YYKVStorageType type;
    if (threshold == 0) {
        type = YYKVStorageTypeFile;
    } else if (threshold == NSUIntegerMax) {
        type = YYKVStorageTypeSQLite;
    } else {
        type = YYKVStorageTypeMixed;
    }
    
    YYKVStorage *kv = [[YYKVStorage alloc] initWithPath:path type:type];
    if (!kv) return nil;
    
    _kv = kv;
    _path = path;
    _lock = dispatch_semaphore_create(1);
    _queue = dispatch_queue_create("com.ibireme.cache.disk", DISPATCH_QUEUE_CONCURRENT);
    _inlineThreshold = threshold;
    _countLimit = NSUIntegerMax;
    _costLimit = NSUIntegerMax;
    _ageLimit = DBL_MAX;
    _freeDiskSpaceLimit = 0;
    _autoTrimInterval = 60;
    
    [self _trimRecursively];
    _YYDiskCacheSetGlobal(self);
    
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_appWillBeTerminated) name:UIApplicationWillTerminateNotification object:nil];
    return self;
}

通过上面看到YYDiskCache是通过threshold(阈值)的大小来设置存储类型,threshold的默认值是1024 * 20,大小也就是20KB.为什么选择20KB?因为作者测试时,得到当单条数小于20k时,数据越小SQLite读取性能越高;单条数据大于20K时,直接写为文件速度会更快一些。

  • threshold == 0:,所有存储数据对象存储为文件

  • threshold == NSUIntegerMax: 所有存储数据对象存储到数据库

  • threshold 为其他值: 根据存储数据对象大小设置存储类型

在YYDiskCache的存储中,YYKVStorage使用YYKVStorageItem存储键值对和元数据

@interface YYKVStorageItem : 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

YYDiskCache读写

  • YYDiskCache写入

    • YYDiskCache在写入时都会存储数据到SQLite数据库中,区别在于当文件名称不为空时, 元数据value不会存入到SQLite中,是存储到文件系统中.
    // YYDiskCache 传入存储对象和key存储到磁盘
    - (void)setObject:(id<NSCoding>)object forKey:(NSString *)key {
        // 判断key是否为空
        if (!key) return;
         // 判断object是否为空
        if (!object) {
             // 如果object为空 从磁盘缓存中删除以key的键的缓存数据x对象
            [self removeObjectForKey:key];
            return;
        }
        
        // 磁盘存储之前,获取扩展数据
        NSData *extendedData = [YYDiskCache getExtendedDataFromObject:object];
        // 初始化磁盘存储的值
        NSData *value = nil;
        // 判断是否存在自定义归档的Block
        if (_customArchiveBlock) {
            // 自定义归档的Block归档并赋值给value
            value = _customArchiveBlock(object);
        } else {
            @try {
                // 调用NSKeyedArchiver归档并赋值给value
                value = [NSKeyedArchiver archivedDataWithRootObject:object];
            }
            @catch (NSException *exception) {
                // nothing to do...
            }
        }
        if (!value) return;
        // 初始化磁盘存储的文件名称
        NSString *filename = nil;
        // 判断存储类型是否等于YYKVStorageTypeSQLite
        if (_kv.type != YYKVStorageTypeSQLite) {
            // 值的长度大于内部阈值  key采用md5加密并赋值给文件名称
            if (value.length > _inlineThreshold) {
                filename = [self _filenameForKey:key];
            }
        }
        
        // 加锁 采用信号量保证线程安全 #define Lock() dispatch_semaphore_wait(self->_lock, DISPATCH_TIME_FOREVER)
        Lock();
        // YYKVStorage保存到磁盘中
        [_kv saveItemWithKey:key value:value filename:filename extendedData:extendedData];
         // 解锁 #define Unlock() dispatch_semaphore_signal(self->_lock)
        Unlock();
    }
    
    // YYKVStorage 存储
    - (BOOL)saveItemWithKey:(NSString *)key value:(NSData *)value filename:(NSString *)filename extendedData:(NSData *)extendedData {
        if (key.length == 0 || value.length == 0) return NO;
        // 类型等于YYKVStorageTypeFile 并且filename为空 返回 不能存储到磁盘
        if (_type == YYKVStorageTypeFile && filename.length == 0) {
            return NO;
        }
        
        // 文件名称不为空 执行文件缓存
        if (filename.length) {
            // 执行文件缓存 返回BOOL值 文件存储失败就返回
            if (![self _fileWriteWithName:filename data:value]) {
                return NO;
            }
            // 存储元数据到SQLite中
            if (![self _dbSaveWithKey:key value:value fileName:filename extendedData:extendedData]) {
                // 存储元数据到SQLite失败 文件缓存也要删除
                [self _fileDeleteWithName:filename];
                return NO;
            }
            return YES;
        } else { // 文件名称为空,不文件缓存
            if (_type != YYKVStorageTypeSQLite) {
                // 存储类型不等于YYKVStorageTypeSQLite,判断是否缓存有key的文件名称,如果有就删除
                NSString *filename = [self _dbGetFilenameWithKey:key];
                if (filename) {
                    [self _fileDeleteWithName:filename];
                }
            }
            // 存储元数据到SQLite中
            return [self _dbSaveWithKey:key value:value fileName:nil extendedData:extendedData];
        }
    }
    
    // 执行SQL语句存储元数据到SQLite中
    - (BOOL)_dbSaveWithKey:(NSString *)key value:(NSData *)value fileName:(NSString *)fileName extendedData:(NSData *)extendedData {
        NSString *sql = @"insert or replace into manifest (key, filename, size, inline_data, modification_time, last_access_time, extended_data) values (?1, ?2, ?3, ?4, ?5, ?6, ?7);";
        ......
          // 当文件名称为空时 存储value数据到SQLite中
        if (fileName.length == 0) {
            sqlite3_bind_blob(stmt, 4, value.bytes, (int)value.length, 0);
        } else {
            // 当文件名称不为空时 不存储value数据到SQLite中,此时文件系统已缓存
            sqlite3_bind_blob(stmt, 4, NULL, 0, 0);
        }
        int result = sqlite3_step(stmt);
        ......
        return YES;
    }
    
  • YYDiskCache读取

    - (id<NSCoding>)objectForKey:(NSString *)key {
        if (!key) return nil;
        Lock();
        // 从YYKVStorage获取缓存YYKVStorageItem对象
        YYKVStorageItem *item = [_kv getItemForKey:key];
        Unlock();
        if (!item.value) return nil;
        
        id object = nil;
        if (_customUnarchiveBlock) {
            // 自定义解档Block存在 执行Block解档
            object = _customUnarchiveBlock(item.value);
        } else {
            @try {
                // 自定义解档Block不存在 执行NSKeyedUnarchiver解档
                object = [NSKeyedUnarchiver unarchiveObjectWithData:item.value];
            }
            @catch (NSException *exception) {
                // nothing to do...
            }
        }
        // 解档对象存储并且存在扩展数据 设置扩展数据到YYDiskCache
        if (object && item.extendedData) {
            [YYDiskCache setExtendedData:item.extendedData toObject:object];
        }
        return object;
    }
    
    - (YYKVStorageItem *)getItemForKey:(NSString *)key {
        if (key.length == 0) return nil;
        // 从SQLite中获取缓存的YYKVStorageItem对象
        YYKVStorageItem *item = [self _dbGetItemWithKey:key excludeInlineData:NO];
        if (item) {
            // 更新key值的访问时间
            [self _dbUpdateAccessTimeWithKey:key];
            // 如果文件名称不为空 去文件系统去获取缓存的value
            if (item.filename) {
                item.value = [self _fileReadWithName:item.filename];
                // value为空 删除此s缓存对象
                if (!item.value) {
                    [self _dbDeleteItemWithKey:key];
                    item = nil;
                }
            }
        }
        return item;
    }
    
  • YYDiskCache的LRU算法

    • 在YYDiskCache中存储的数据都是在SQLite中,当YYDiskCache的_trimRecursively_trimInBackground检查到需要当磁盘总开销、缓存对象总数量、存活时间、磁盘空闲超过限定值,执行删除操作。通过last_access_time标识缓存数据的优先级,执行SQL语句返回last_access_time最后访问时间靠前的缓存对象执行删除操作。

      - (BOOL)removeItemsToFitCount:(int)maxCount {
         ......
          NSArray *items = nil;
          BOOL suc = NO;
          do {
              int perCount = 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;
      }
      
      - (BOOL)removeItemsToFitSize:(int)maxSize {
         ......
          NSArray *items = nil;
          BOOL suc = NO;
          do {
              int perCount = 16;
              items = [self _dbGetItemSizeInfoOrderByTimeAscWithLimit:perCount];
              for (YYKVStorageItem *item in items) {
                  if (total > maxSize) {
                      if (item.filename) {
                          [self _fileDeleteWithName:item.filename];
                      }
                      suc = [self _dbDeleteItemWithKey:item.key];
                      total -= item.size;
                  } else {
                      break;
                  }
                  if (!suc) break;
              }
          } while (total > maxSize && 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;";
          // 创建预处理SQL语句对象
          sqlite3_stmt *stmt = [self _dbPrepareStmt:sql];
          if (!stmt) return nil;
          // 绑定需要查询的数量到预处理SQL语句对象中
          sqlite3_bind_int(stmt, 1, count);
          
          NSMutableArray *items = [NSMutableArray new];
          // do whileh循环执行预处理SQL语句,遍历得到的结果
          do {
              // 执行预处理SQL语句
              int result = sqlite3_step(stmt);
              // SQLITE_ROW:查询到一行数据, 获取数据添加到items
              // SQLITE_DONE:结果集遍历完成,跳出循环
              // result等于其他数据时, 跳出循环
              if (result == SQLITE_ROW) {
                  // 获取key
                  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;
      }
      
      

YYCache提高高性能缓存的框架的其他操作

  • CFMutableDictionaryRef: CFMutableDictionaryRef作为CoreFoundation框架的函数比NSMutableDictionary有更高的性能。

  • 缓存的自动清理和释放都放到子线程处理:通过GCD把需要释放的对象捕捉到指定队列所在线程的Block中释放。下面的代码显示出来这样的操作 [holder count]; [node class]; 是捕捉对象到Block 避免编译器警告。

  • // 删除所有内存缓存
    - (void)removeAll {
        ......
        if (CFDictionaryGetCount(_dic) > 0) {
            CFMutableDictionaryRef holder = _dic;
            // holder 在指定的线程中释放
            if (_releaseAsynchronously) {
                dispatch_queue_t queue = _releaseOnMainThread ? dispatch_get_main_queue() : YYMemoryCacheGetReleaseQueue();
                dispatch_async(queue, ^{
                    CFRelease(holder); // 捕捉holder到指定队列所在线程的Block中释放
                });
            } else if (_releaseOnMainThread && !pthread_main_np()) {
                dispatch_async(dispatch_get_main_queue(), ^{
                    CFRelease(holder); // 捕捉holder到指定队列所在线程的Block中释放
                });
            } else {
                CFRelease(holder);
            }
        }
    }
    - (void)_trimToCount:(NSUInteger)countLimit {
        ......
        NSMutableArray *holder = [NSMutableArray new];
          ......
        if (holder.count) {
            dispatch_queue_t queue = _lru->_releaseOnMainThread ? dispatch_get_main_queue() : YYMemoryCacheGetReleaseQueue();
            dispatch_async(queue, ^{
                [holder count]; // 捕捉holder到指定队列所在线程的Block中释放
            });
        }
    }
    
    - (void)setObject:(id)object forKey:(id)key withCost:(NSUInteger)cost {
       ......
        _YYLinkedMapNode *node = CFDictionaryGetValue(_lru->_dic, (__bridge const void *)(key));
        if (_lru->_totalCount > _countLimit) {
            _YYLinkedMapNode *node = [_lru removeTailNode];
            if (_lru->_releaseAsynchronously) {
                dispatch_queue_t queue = _lru->_releaseOnMainThread ? dispatch_get_main_queue() : YYMemoryCacheGetReleaseQueue();
                dispatch_async(queue, ^{
                    [node class]; // 捕捉node到指定队列所在线程的Block中释放
                });
            } else if (_lru->_releaseOnMainThread && !pthread_main_np()) {
                dispatch_async(dispatch_get_main_queue(), ^{
                    [node class]; // 捕捉node到指定队列所在线程的Block中释放
                });
            }
        }
        ......
    }
    
    
  • 使用性能更好的 SQLite : SQLite官网最新的SQlite版本比iOS 系统自带的 sqlite3.dylib 性能要高很多。

  • #if __has_include(<sqlite3.h>)
    #import <sqlite3.h>
    #else
    #import "sqlite3.h"
    #endif
    
  • 使用性能更好的锁 YYCache采用dispatch_semaphorepthread_mutex 两种锁用于线程同步,这两种的性能是仅次于OSSpinLockos_unfair_lock。作者最开始在内存缓存中使用的OSSpinLockOSSpinLock由于线程不安全问题被弃用。作为性能最好的锁os_unfair_lock,这个是iOS10发布的,可能作者没有更新此框架或者考虑iOS10以下的原因,所以没有用os_unfair_lock

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,117评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,328评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,839评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,007评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,384评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,629评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,880评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,593评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,313评论 1 243
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,575评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,066评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,392评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,052评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,082评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,844评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,662评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,575评论 2 270

推荐阅读更多精彩内容