iOS YYMemoryCache

【iOS YYMemoryCache】YYMemoryCache
YYCache是由ibireme 开发的组件库 YYKit 中的一款独立的高性能 iOS 缓存框架。
YYMemoryCache则是YYCache 中的一种存储键值对的快速内存缓存框架。与 NSDictionary 相比,键被保留而不被复制。API 和性能类似于NSCache,使用互斥锁保证所有方法都是线程安全的。另外,缓存内部用双向链表和 NSDictionary 实现了 LRU 淘汰算法
YYMemoryCache 和 NSCache 的不同点:

  • YYMemoryCache使用 LRU(最近最少使用)来删除对象; NSCache 的eviction方法是非确定性的。
  • YYMemoryCache可以通过成本、数量和年龄来控制; NSCache 的限制是不精确的。
  • YYMemoryCache可以配置为在收到内存警告或应用程序进入后台时自动驱逐对象。
#pragma mark - Access Methods ///============================================================================= /// @name Access Methods ///=============================================================================/** 返回一个布尔值,指示给定键是否在缓存中。 */ - (BOOL)containsObjectForKey:(id)key; /** 返回与给定键关联的值。 */ - (nullable id)objectForKey:(id)key; /** 设置缓存中指定键的值(0 成本)。 */ - (void)setObject:(nullable id)object forKey:(id)key; /** 设置缓存中指定key的值,并关联key-value 与指定的成本配对。 */ - (void)setObject:(nullable id)object forKey:(id)key withCost:(NSUInteger)cost; - (void)removeObjectForKey:(id)key; - (void)removeAllObjects;

containsObjectForKey的实现
- (BOOL)containsObjectForKey:(id)key { if (!key) return NO; pthread_mutex_lock(&_lock); //使用互斥锁保证read线程安全 BOOL contains = CFDictionaryContainsKey(_lru->_dic, (__bridge const void *)(key)); pthread_mutex_unlock(&_lock); return contains; }

objectForKey的实现
- (id)objectForKey:(id)key { if (!key) return nil; //非空判断 pthread_mutex_lock(&_lock); //线程安全 _YYLinkedMapNode *node = CFDictionaryGetValue(_lru->_dic, (__bridge const void *)(key)); if (node) { node->_time = CACurrentMediaTime(); [_lru bringNodeToHead:node]; //将使用后的节点置为头节点 } pthread_mutex_unlock(&_lock); return node ? node->_value : nil; }

setObject:forKey:withCost:的实现
- (void)setObject:(id)object forKey:(id)key withCost:(NSUInteger)cost { if (!key) return; if (!object) { [self removeObjectForKey:key]; return; } pthread_mutex_lock(&_lock); _YYLinkedMapNode *node = CFDictionaryGetValue(_lru->_dic, (__bridge const void *)(key)); NSTimeInterval now = CACurrentMediaTime(); if (node) {//调整cost , 若存在队列中 _lru->_totalCost -= node->_cost; _lru->_totalCost += cost; node->_cost = cost; node->_time = now; node->_value = https://www.it610.com/article/object; [_lru bringNodeToHead:node]; } else { node = [_YYLinkedMapNode new]; node->_cost = cost; node->_time = now; node->_key = key; node->_value = https://www.it610.com/article/object; [_lru insertNodeAtHead:node]; } if (_lru->_totalCost > _costLimit) { dispatch_async(_queue, ^{ [self trimToCost:_costLimit]; }); } if (_lru->_totalCount > _countLimit) { _YYLinkedMapNode *node = [_lru removeTailNode]; //进行淘汰 if (_lru->_releaseAsynchronously) { dispatch_queue_t queue = _lru->_releaseOnMainThread ? dispatch_get_main_queue() : YYMemoryCacheGetReleaseQueue(); // dispatch_async(queue, ^{ [node class]; //hold and release in queue在特定的queue里释放 /* 应该是node在执行完这个方法后就出了作用域了,reference会减1, 但是此时node不会被dealloc,因为block 中retain了node, 使得node的reference count为1,当执完block后,node的reference count又-1, 此时node就会在block对应的queue上release了。 */ }); } else if (_lru->_releaseOnMainThread && !pthread_main_np()) { dispatch_async(dispatch_get_main_queue(), ^{ [node class]; //hold and release in queue }); } } pthread_mutex_unlock(&_lock); }

在 _YYLinkedMap 类中使用 双链表+哈希表 实现LRU算法
插入:当Cache未满时,新的数据项只需插到双链表头部即可
替换:当Cache已满时,将新的数据项插到双链表头部,并删除双链表的尾结点即可
查找:每次数据项被查询到时,都将此数据项移动到链表头部
- (void)insertNodeAtHead:(_YYLinkedMapNode *)node { //使用字典保存链表节点node CFDictionarySetValue(_dic, (__bridge const void *)(node->_key), (__bridge const void *)(node)); //叠加该缓存开销到内存总开销 _totalCost += node->_cost; //总缓存数+1 _totalCount++; if (_head) { //如果存在头部节点,则将node 置为 头节点 node->_next = _head; _head->_prev = node; _head = node; } else {//如果不存在,则将node置为当前唯一节点 _head = _tail = node; } }- (void)bringNodeToHead:(_YYLinkedMapNode *)node { if (_head == node) return; //如果当前节点是表头if (_tail == node) {//如果当前节点是尾节点 _tail = node->_prev; //将node的前驱结点指向尾节点 _tail->_next = nil; //将尾节点的后继节点置空 } else { node->_next->_prev = node->_prev; //将node指向的下一个节点的前驱节点,指向node的前驱节点 node->_prev->_next = node->_next; //将node指向的上一个节点的后继节点,指向node的后继节点,从链表中剥离出node } node->_next = _head; //将node的后继节点指向当前头节点 node->_prev = nil; //将node的前驱节点置空 _head->_prev = node; //此时的头节点的前驱指向node _head = node; //将此时的node置为头节点 }- (void)removeNode:(_YYLinkedMapNode *)node {//前提是node为链表中的一个节点 CFDictionaryRemoveValue(_dic, (__bridge const void *)(node->_key)); _totalCost -= node->_cost; //调整总开销 _totalCount--; //调整总缓存数 if (node->_next) node->_next->_prev = node->_prev; //如果node 存在后继节点,则将node的后继节点的前驱指向node的前驱结点 if (node->_prev) node->_prev->_next = node->_next; //如果node 存在前驱节点,则将node的前驱节点的后继指向node的后继节点 if (_head == node) _head = node->_next; //如果node是头节点,则将node的后继节点置为头节点 if (_tail == node) _tail = node->_prev; //如果node是尾节点, 则将node的前驱结点置为尾节点 }

    推荐阅读