前言
本篇用来记录下阅读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
未完待续