美文网首页首页投稿iOS基础知识
【内存篇】为什么选择YYCache

【内存篇】为什么选择YYCache

作者: 李周 | 来源:发表于2020-03-21 01:23 被阅读0次
    最近走在学习有关移动端内存的路上,这不慢悠悠的走到了YYCache的路标下。那就从自己的角度先说说为什么在项目中选择了YYCache做缓存。

    准备

    LRU缓存机制是什么?

    一般的数据在存储时都会考虑采用栈(先进后出) 还是 队列(先进先出)的模式,举例以队列(先进先出)的模式管理缓存中的数据,那么如下图:


    先进先出模式

    设置缓存空间只能存储数量为2时,当A\B存储后再需要空间存储C时,那么就只能先移除A数据。换句话说,每次删除的都是优先级低的数据,而现在队列的处理方式相当于 (缓存加入的时间) == (优先级),越先加入的数据优先级会越低,也就会越先被删除。

    那么如果 A 在插入 C数据时,被访问了多次,而且在插入C数据之后也访问了多次,按照队列(先进先出)的模式会不会导致 A 不断的移除又不断的加入呢?

    确实会这样,那么 删除的优先级 就不单单等同于缓存第一次加入的时间,而是等同于每次刷新的时间,每次调用Get时都不断的刷新缓存数据的时间,也就是LRU(least-recently-used)缓存机制。


    LRU缓存机制
    基于以上的几个问题就能LRU缓存机制的实现原理:

    ① A虽然是最先缓存的数据,优先级最低,但是每次访问都能必须将A放置到最高优先级的位置.
    ② 涉及到数据的多次位置变更,所以链式存储优于线性存储。但是链表在搜索操作时的时间复杂度为0(n),所以必须有辅助工具: hash表 + 双向链表。

    用图就能很简单的描述出整体的思路:


    LRU缓存机制实现思路

    LeetCode上的N.146 LRU缓存机制来验证以上的思路(Python版):
    ① 结点 Node

    class Node: # 构建结点
        def __init__(self):
            self.key = 0
            self.val = 0
            self.prev = None  # 前面结点
            self.next = None  # 后面结点
    

    ② 链表管理

    class LRUCache:
    
        def __add(self, node): #添加node
            node.next = self.__header.next
            node.prev = self.__header
            self.__header.next.prev = node
            self.__header.next = node
        def __remove(self, node): # 移除node
            node.prev.next = node.next
            node.next.prev = node.prev
    
        def __move_to_end(self, node): #移动到前面,也就是将优先级设置最高
            self.__remove(node)
            self.__add(node)
        def __remove_tail(self):
            res = self.__tail.prev
            self.__remove(res)
            return res
    
    
        def __init__(self,capacity):
            self.capacity = capacity #缓存容量
            self.dic = {}  # hash
            self.size = 0
            self.__header, self.__tail = Node(), Node()
            self.__header.next = self.__tail
            self.__tail.prev = self.__header
    
    
        def get(self, key): # get Node
            node = self.dic.get(key, None)
            if node is not None:
                self.__move_to_end(node)
                return node.val
            return -1
        def put(self,key, val):  # push new Node
            node = self.dic.get(key, None)
            if node is None:
                node = Node()
                node.key = key
                node.val = val
                self.__add(node)
                self.dic[key] = node
                self.size += 1
            else:
                node.val = val
                self.__move_to_end(node)
            if self.size > self.capacity:
                tail = self.__remove_tail()
                self.dic.pop(tail.key)
    

    查找、插入、删除的时间复杂度都为O(1).


    Memory Cache和On-disk Cache有什么差别?

    Memory Cache就是RAM(运行内存),CPU能直接访问,但是不能掉电存储。
    on-Disk Cache 是ROM,不能被CPU直接访问,能掉电存储,以文件File 或 数据库SQlite等形式进行存储。

    YYCache

    架构
    Memory Cache
    是一种快速存储键值对的内存缓存。相对于NSDictionary,键会被retained而不会capied。API的性能和NSCache类似,所有的方法都是线程安全
    与NSCache不同的地方:
    ① 使用了LRU缓存机制移除对象
    ② 能被cost花销、count数量和age缓存时间控制
    ③ 能配置当接收到内存警告或App进入后台时自动释放对象
    

    从以上对LRU缓存机制的讲解入手:
    ① 结点 _YYLinkedMapNode

    @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
    

    ② 链表管理 _YYLinkedMap

    @interface _YYLinkedMap : NSObject {
        @package 
        CFMutableDictionaryRef _dic; // 哈希表直接在map的内部,并没有并行放在外部
        NSUInteger _totalCost;
        NSUInteger _totalCount;
        _YYLinkedMapNode *_head; // 头部指针
        _YYLinkedMapNode *_tail; // 尾部指针
        BOOL _releaseOnMainThread;
        BOOL _releaseAsynchronously;
    }
    

    通过CoreFoundation 提高了_dic中的操作性能,在该链表类中也同样包括对插入、删除结点相关的方法:



    ③ 实际操作 YYMemoryCache

    • 通知的处理
    shouldRemoveAllObjectsOnMemoryWarning // 默认为YES,在接收到内存警告时移除所有的对象
    shouldRemoveAllObjectsWhenEnteringBackground // 默认为YES,在App进入后台时移除所有的对象
    
    • 线程的处理
    releaseOnMainThread  //默认为NO, 如果为YES就会在主线程中释放,只有当包含了如UIView/CALayer只能在主线程中操作的对象才设置YES
    releaseAsynchronously //默认为YES,会异步的释放键值对避免阻塞当前方法
    

    在release释放某个node时,会进入其他线程中让block持有holder,然后由该queue异步释放该holder中的node

    if (holder.count) {
            dispatch_queue_t queue = _lru->_releaseOnMainThread ? dispatch_get_main_queue() : YYMemoryCacheGetReleaseQueue();
            dispatch_async(queue, ^{
                [holder count]; // release in queue
            });
        }
    

    相关文章

      网友评论

        本文标题:【内存篇】为什么选择YYCache

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