美文网首页
YYMemoryCache源码分析

YYMemoryCache源码分析

作者: yimiao | 来源:发表于2022-03-01 16:02 被阅读0次

    YYCache 缓存框架

    • YYMemoryCache 内存中的缓存
    • YYDiskCache
    • YYKVStorage

    YYMemoryCache

    内存缓存功能设计目标

    • 对外接口是key,value进行缓存和获取
    • 能够限制数量大小和总消耗cost
    • 获取缓存时,支持LRU(最近最少使用),MRU(最近最多使用),Key-Value快速获取

    实现基本思路

    • 先用一个新的数据结构node对value数据进行封装,丰富携带信息,因为value需要很多个信息绑定在一起,包括时间,key,最近使用时间,开销等
    • 使用CFMutableDictionaryRef存储<key,node>,非常方便通过key->node->value实现快速获取
    • 同时又使用双向链表的设计,每一个node都有pre和next指针,根据使用情况将各个node进行排序。整个链表只需要头head指针,一个尾tail指针,就能同时实现MRU和LRU。

    双向链表,节点的数据结构

    /** 每一个数据节点的结构 */
    @interface _YYLinkedMapNode : NSObject {
        @package
        __unsafe_unretained _YYLinkedMapNode *_prev; // retained by dic
        __unsafe_unretained _YYLinkedMapNode *_next; // retained by dic
        id _key;
        id _value;
        NSUInteger _cost;
        NSTimeInterval _time;
    }
    @end
    
    /** 整个链表的结构 */
    @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;
    }
    

    双向链表的头插入

    1. 先添加到dic,计算cost和count
    2. 判断是否已存在head
      2.1 已存在,则把当前node添加到head的前面
      2.2 不存在,那就是第一个,head和tail都是node
    - (void)insertNodeAtHead:(_YYLinkedMapNode *)node {
        CFDictionarySetValue(_dic, (__bridge const void *)(node->_key), (__bridge const void *)(node));
        _totalCost += node->_cost;
        _totalCount++;
        if (_head) {
            node->_next = _head;
            _head->_prev = node;
            _head = node;
        } else {
            _head = _tail = node;
        }
    }
    

    双向链表把节点移动到第一位

    1. 如果本来就是第一位,无需操作
    2. 先把当前节点从链表中删除
      2.1 删除节点是最后一位?则尾部是上一位
      2.2 讲当前节点的下一位和上一位进行关联
    3. 最后把当前节点替换成第一位
    - (void)bringNodeToHead:(_YYLinkedMapNode *)node{
        if (_head == node) return;
        
        if (_tail == node) {
            _tail = node->_prev;
            _tail->_next = nil;
        } else {
            node->_next->_prev = node->_prev;
            node->_prev->_next = node->_next;
        }
        node->_next = _head;
        node->_prev = nil;
        _head->_prev = node;
        _head = node;
    }
    

    双向链表的删除

    1. 从dic中删除key,计算cost,count
    2. 处理next节点的prev
    3. 处理prev节点的next
    4. 是否删除的是head节点
    5. 是否删除的是tail节点
    - (void)removeNode:(_YYLinkedMapNode *)node {
        CFDictionaryRemoveValue(_dic, (__bridge const void *)(node->_key));
        _totalCost -= node->_cost;
        _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;
    }
    

    通过key获取缓存内容

    1. 先通过key从dic中快速找出node
    2. 找出来后,更新时间并且移动到链表的头部
    3. 返回node->value即内容
    - (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;
    }
    

    通过key添加缓存内容value

    1. 没有key,啥也没法做
    2. 没有object,表示删除key,
    3. 如果当前key本身就有缓存,替换旧的数据,再次移动到链表头部
    4. 如果当前key是新的,创建新的node,添加到链表头部
    5. 超出cost/count,从tail开始清理缓存数据
    - (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) {
            _lru->_totalCost -= node->_cost;
            _lru->_totalCost += cost;
            node->_cost = cost;
            node->_time = now;
            node->_value = object;
            [_lru bringNodeToHead:node];
        } else { 
            node = [_YYLinkedMapNode new];
            node->_cost = cost;
            node->_time = now;
            node->_key = key;
            node->_value = object;
            [_lru insertNodeAtHead:node];
        }
        if (_lru->_totalCost > _costLimit) {
            dispatch_async(_queue, ^{
                [self trimToCost:_costLimit];
            });
        }
        if (_lru->_totalCount > _countLimit) {
            _YYLinkedMapNode *node = [_lru removeTailNode];
            if (_lru->_releaseAsynchronously) {
                dispatch_queue_t queue = _lru->_releaseOnMainThread ? dispatch_get_main_queue() : YYMemoryCacheGetReleaseQueue();
                dispatch_async(queue, ^{
                    [node class]; //hold and release in queue
                });
            } else if (_lru->_releaseOnMainThread && !pthread_main_np()) {
                dispatch_async(dispatch_get_main_queue(), ^{
                    [node class]; //hold and release in queue
                });
            }
        }
        pthread_mutex_unlock(&_lock);
    }
    

    iOS涉及知识点

    • CFMutableDictionaryRef 的使用,与NSMutableDictionary的区别?
    • MainThread和dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0)的区别
    • pthread_mutex_t 线程锁的使用,
    • 通过dispatch_async让一个已经没有用的对象在指定线程执行一个无用的代码进行释放

    相关文章

      网友评论

          本文标题:YYMemoryCache源码分析

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