美文网首页
YYKit-YYCache源码分析

YYKit-YYCache源码分析

作者: 狗不理火锅 | 来源:发表于2019-05-31 17:29 被阅读0次

    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

    相关文章

      网友评论

          本文标题:YYKit-YYCache源码分析

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