YYKit源码阅读 - YYCache

前言

本篇用来记录下阅读YYKit中YYCache的一些理解和收获,着重解决两个问题:

  1.YYCache在内存和磁盘各自存取方式。

  2.YYCache使用怎样的数据结构来进行数据的存储。怎样确保多线程下的数据操作的安全性。

首先我们来看一下YYCache包含的文件。在本篇中我们并不像之前那样每个属性,每个方法都展示出来解释,主要还是针对问题。

YYMemoryCache

YYMemoryCache是存储键值对的快速内存缓存,区别于NSDictionary,其键是retain而非copy(原因是因为使用的CFDictionary存储)。它的API与NSCache相似并且都是线程安全的。但它也与NSCache有一些不同点:

  • YYMemoryCache使用LRU也就是最近最少使用来移除objects;NSCache是不确切的。
  • YYMemoryCache可以通过对象数量,对象空间总量以及失效时间来控制;NSCache在此也是不确切的。
  • YYMemoryCache可以配置在当收到内存警告或者app进入后台是来自动移除掉objects。

同时YYMemoryCache的访问方法的时间复杂度均为O(1)。

在.m文件中我们发现有一个_YYLinkedMap对象

其内包含了一个可变字典,一个head节点,一个tail节点以及其他属性。这里的可变字典使用的是CoreFoundation对象,更加底层,性能更好。字典内保存的是_YYLinkedMapNode对象

包含一个前驱节点,一个后继节点。看到这两个名词就知道了使用的链表结构而且是双向链表。但是使用双向链表的目的是什么呢?我们来看下

_YYLinkedMap中定义的方法 

这里我们主要来看下insertNodeAtHead和bringNodeToHead两个方法的实现

- (void)insertNodeAtHead:(_YYLinkedMapNode *)node {
    //将node存入字典中
    CFDictionarySetValue(_dic, (__bridge const void *)(node->_key), (__bridge const void *)(node));
    //空间总量增加
    _totalCost += node->_cost;
    //对象数量增加
    _totalCount++;
    
    //如果已经存在head
    if (_head) {
        //将新添加的node作为head
        node->_next = _head;
        _head->_prev = node;
        _head = node;
    } else {
        //将新添加的node同时作为head和tail
        _head = _tail = node;
    }
}

- (void)bringNodeToHead:(_YYLinkedMapNode *)node {
    //node为head就不操作
    if (_head == node) return;
    
    //分别处理node为tail和node为中间节点的情况
    if (_tail == node) {
        _tail = node->_prev;
        _tail->_next = nil;
    } else {
        node->_next->_prev = node->_prev;
        node->_prev->_next = node->_next;
    }
    //node变更为head
    node->_next = _head;
    node->_prev = nil;
    _head->_prev = node;
    _head = node;
}

其中head是作为MRU的,而tail是作为LRU的。每次从字典中取出缓存时就将此次所取node移到链表的头部,而尾部就是最不常用的node。

我们从YYMemoryCache的成员变量中可以看到有一个pthread_mutex_t,这是一个互斥锁。互斥锁常用的几个方法大概有下面几个

  • pthread_mutex_init,锁的初始化

  • pthread_mutex_lock,加锁

  • pthread_mutex_unlock,解锁

  • pthread_mutex_trylock,pthread_mutex_lock的非阻塞版本

基本上YYMemoryCache所有暴露在外的属性以及隐藏在内的dictionary的操作均存在锁操作。举个例子

- (void)_trimToCost:(NSUInteger)costLimit {
    BOOL finish = NO;
    pthread_mutex_lock(&_lock);
    if (costLimit == 0) {
        [_lru removeAll];
        finish = YES;
    } else if (_lru->_totalCost <= costLimit) {
        finish = YES;
    }
    pthread_mutex_unlock(&_lock);
    if (finish) return;
    
    NSMutableArray *holder = [NSMutableArray new];
    while (!finish) {
        if (pthread_mutex_trylock(&_lock) == 0) {
            if (_lru->_totalCost > costLimit) {
                _YYLinkedMapNode *node = [_lru removeTailNode];
                if (node) [holder addObject:node];
            } else {
                finish = YES;
            }
            pthread_mutex_unlock(&_lock);
        } else {
            usleep(10 * 1000); //10 ms
        }
    }
    if (holder.count) {
        dispatch_queue_t queue = _lru->_releaseOnMainThread ? dispatch_get_main_queue() : YYMemoryCacheGetReleaseQueue();
        dispatch_async(queue, ^{
            [holder count]; // release in queue
        });
    }
}

该方法的目的是根据设置的最大cost来删除缓存。方法里面有两个值得注意的地方一个是使用了usleep函数。看到usleep一定会想到sleep,区别在于usleep一般以ns作为计量单位而当休眠时间达到s的量级会使用sleep。还有就是将holder数组捕获到queue的block内用于在置顶queue中释放holder。

YYDiskCache

未完待续 

posted @ 2018-12-13 17:31  清水00  阅读(334)  评论(0编辑  收藏  举报