YYKit-YYCache source code analysis

YYKit-YYCache source code analysis

YYCache is a high performance frame buffer iOS is YYKit one of the components.

Framework architecture

  • YYCache: The outermost frame interface and the internal YYMemeoryCache YYDiskCache for memory caching and disk caching

  • YYMemoryCache: memory caching, caching mechanism LRU algorithm using a doubly linked list

  • YYDiskCache: disk cache, file storage and use SQLite database storage, support for asynchronous operations

  • YYKVOStorage: YYDiskCache underlying concrete realization, for disk cache

Detailed YYCache API

  • YYCache internal use YYMemeoryCache and YYDiskCache for memory caching and disk caching, API NSCache basic and consistent, all methods are thread safe.

    • YYCache Properties and Methods

    • @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 way to achieve

  • - (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

  • Cache elimination algorithm : cache support LRU (least-recently-used) out algorithm
  • Cache-Control : supports multiple cache control method: the total quantity, total size, survival time

YYMemeryCache caching algorithm eliminated

LRU (least-recently-used) least recently used algorithm

  • The core idea: if the data has recently been visited, then the future is also higher chance of being accessed
  • Strategies: Data recently used on the front, so that data is not used for a long time in the back, out deleted

YYMemeryCache specific implementation of the LRU algorithm:

Implemented using a doubly linked list in YYMemeryCache the LRU way, the insertion data added to the list, or use the front, deleting data is deleted from the cache memory rearmost list. Doubly linked list by _YYLinkedMapNode and _YYLinkedMapNode to achieve

  • _YYLinkedMapNode: list node specific information contains cached data

    @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: objectlist, with memory cache to store and manage all data nodes

    @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: objectlist realization

    @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 cache cleaning strategies:

  • init method

    - (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 : Recursive clean up memory caching method

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

    From the above code can be seen in _trimRecursively YYMemeryCache call init method, which from its specific implementation, it can be seen a recursive call to clean up the memory cache, the total size, the total number, the access time to the cache control.

YYMemeryCache: listening UIApplicationDidReceiveMemoryWarningNotification and UIApplicationDidEnterBackgroundNotification two notifications, when notified, remove all memory cache.

  • - (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 thread-safe

    • Memory cache in YYMemeryCache add, delete, query operations that are using pthread_mutex mutex to the thread synchronization, to ensure that all methods are thread-safe.

      - (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 is a thread-safe disk cache, which stores key-value pairs SQLite support and file system storage (similar to NSURLCache disk cache.

  • Cache elimination algorithm : cache support LRU (least-recently-used) out algorithm
  • Cache-Control : supports multiple cache control method: the total quantity, total size, survival time
  • Automatically delete the cache data can be configured when there is no free disk space
  • Size automatically obtain the type of object according to the buffer store data objects

Embodied YYDiskCahce storage is accomplished by YYKVStroage, there is enumerated in YYKVStorage YYKVStorageType , this memory type is provided

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

Next, look YYDiskCahce initialization method, we can see the type of call stored settings and other methods in which

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

Through the above see YYDiskCache is set by the size of the storage type threshold (threshold), the default threshold value is 1024 * 20, size is 20KB. Why 20KB? OF test because, when a single to give the number is less than 20k, the smaller the data read SQLite higher performance; single data greater than 20K, the direct write speed faster file.

  • == 0 threshold: , all stored data objects stored as a file

  • threshold == NSUIntegerMax: all stored data objects stored in the database

  • other threshold values: The memory size of the object data set storage type

In YYDiskCache of storage, YYKVStorage used YYKVStorageItem store metadata key-value pairs and

@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 read and write

  • YYDiskCache write

    • YYDiskCache SQLite data are stored in the database is written, except that when the file name is not empty, the value is not stored into the metadata in SQLite, it is stored in the file system.
    // 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 read

    - (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 the LRU algorithm

    • Data stored in SQLite YYDiskCache is, when the YYDiskCache _trimRecursively and _trimInBackground checked when the disk needs to overhead, the total number of cache object, survival time, idle disk exceeds the limit value, the delete operation. By last_access_time priority identification cached data, execute SQL statement returns last_access_time cached objects forward last access time of a delete operation.

      - (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 high performance cache to improve operation of the other frame

  • CFMutableDictionaryRef: CFMutableDictionaryRef CoreFoundation frame as a function of higher performance than NSMutableDictionary.

  • Automatic cleanup and release the cache are placed in sub-threading: to capture the specified thread queue where the GCD through the object to be released Block release. The following code is displayed such operation [holder count]; [node class ]; to capture an object to avoid compiler warnings 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中释放
                });
            }
        }
        ......
    }
    
    
  • The use of better performance of SQLite: SQLite official website SQlite latest version comes with iOS system sqlite3.dylib performance ratio is much higher.

  • #if __has_include(<sqlite3.h>)
    #import <sqlite3.h>
    #else
    #import "sqlite3.h"
    #endif
    
  • The use of better performing lock YYCache using dispatch_semaphore and pthread_mutex other kinds of locks for thread synchronization, both the performance is second only to OSSpinLock and os_unfair_lock . On the beginning of the use of in-memory cache OSSpinLock , OSSpinLock due to the insecurity in the thread it was abandoned. As best performance lock os_unfair_lock , this is iOS10 published author may not update this framework or consider iOS10 following reasons, there is no use os_unfair_lock .

Reproduced in: https: //www.jianshu.com/p/529d286eb53b

Guess you like

Origin blog.csdn.net/weixin_33938733/article/details/91194562