一直想写一写自己读源码的感受与理解,可惜一直未付诸行动。最近闲来无事,索性整理一下,记录于此.
YYCache
YYCahce 包括 内存缓存(YYMemoryCache) 与 磁盘缓存(YYDiskCache), 而且都是线程安全的!
YYMemoryCache 内存缓存
YYMemoryCache用于对内存缓存进行管理,因为需要高效查询数据,所以 key-value 模式储存。为什么不直接使用字典呢,因为需要保证线程安全。而为什么不直接使用 NSCache 呢,因为 YYMemoryCache 基于自定义的双向链表,提供一种 LRU(least-recently-used: 最近最久少使用的会先删除)
淘汰算法去操作缓存,进行性能优化。
_YYLinkedMap
为了实现算法,YYMemoryCache 内部封装了一个 _YYLinkedMap
的双向链表1
2
3
4
5
6
7
8
9
10@interface _YYLinkedMap : NSObject {
@package
CFMutableDictionaryRef _dic; // 哈希字典,存放 (key : _YYLinkedMapNode)
NSUInteger _totalCost; // 缓存占用内存总大小
NSUInteger _totalCount; // 缓存节点个数
_YYLinkedMapNode *_head; // 头结点,最近使用最多的节点 MRU
_YYLinkedMapNode *_tail; // 尾节点,最近使用最少的节点 LRU
BOOL _releaseOnMainThread; // 在主线程释放_dic
BOOL _releaseAsynchronously; // 在异步线程释放_dic
}
_YYLinkedMapNode
链表节点数据
1 | @interface _YYLinkedMapNode : NSObject { |
CFMutableDictionaryRef 操作: CoreFoundation 的字典,其性能更好
1
2
3
4
5
6
7
8/// 初始化
_dic = CFDictionaryCreateMutable(CFAllocatorGetDefault(), 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
/// 存储数据
CFDictionarySetValue(_dic, (__bridge const void *)(node->_key), (__bridge const void *)(node));
/// 根据key删除数据
CFDictionaryRemoveValue(_dic, (__bridge const void *)(node->_key));
/// 获取字典元素个数
CFDictionaryGetCount(_dic);
2._YYLinkedMap
方法列表:
1
2
3
4
5
6
7
8
9
10
/// 插入头节点
- (void)insertNodeAtHead:(_YYLinkedMapNode *)node;
/// 将节点设为头结点
- (void)bringNodeToHead:(_YYLinkedMapNode *)node;
/// 删除节点
- (void)removeNode:(_YYLinkedMapNode *)node;
/// 删除尾节点
- (_YYLinkedMapNode *)removeTailNode;
/// 删除所有数据
- (void)removeAll;
YYMemoryCache
1 | pthread_mutex_t _lock; |
pthread_mutex_t 互斥锁,保证线程安全
操作方法:
1 | pthread_mutex_init(&_lock, NULL); // 初始化 |
Init YYMemoryCache
1 | - (instancetype)init { |
边界检测:内存检测
YYMemoryCache 有一个边界检测机制,从 YYMemoryCache 初始化就开始工作(类似于定时器,隔一段时间就执行一次操作,但不是使用定时器),使用递归调用1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17- (void)_trimRecursively {
__weak typeof(self) _self = self;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(_autoTrimInterval * NSEC_PER_SEC)), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
__strong typeof(_self) self = _self;
if (!self) return;
[self _trimInBackground]; // 边界检测
[self _trimRecursively]; // 递归调用自己,实现定时器的效果
});
}
- (void)_trimInBackground {
dispatch_async(_queue, ^{
[self _trimToCost:self->_costLimit]; // 缓存大小是否超过预定值,超过则从尾节点开始删除
[self _trimToCount:self->_countLimit];// 缓存个数是否超过预定值,超过则从尾节点开始删除
[self _trimToAge:self->_ageLimit]; // 遍历节点,删除和now相差 _ageLimit 的节点
});
}
_trimToCost
_trimToCount
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38- (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); //休息10s,在进行检测是否可以获得锁
}
}
if (holder.count) {
dispatch_queue_t queue = _lru->_releaseOnMainThread ? dispatch_get_main_queue() : YYMemoryCacheGetReleaseQueue();
dispatch_async(queue, ^{
[holder count]; // 在这里 释放 node,防止分散控制
});
}
}
// 与 `_trimToCost` 差不多的逻辑
- (void)_trimToCount:(NSUInteger)countLimit {
...
}
这里有个tip,就是让block捕获一个局部变量,然后扔到后台队列去随便发送个消息以避免编译器警告,这样就可以让对象在后台线程销毁:
1 | NSMutableArray *holder = [NSMutableArray new]; |
_trimToAge
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19- (void)_trimToAge:(NSTimeInterval)ageLimit {
...
NSMutableArray *holder = [NSMutableArray new];
while (!finish) {
if (pthread_mutex_trylock(&_lock) == 0) {
if (_lru->_tail && (now - _lru->_tail->_time) > ageLimit) {
_YYLinkedMapNode *node = [_lru removeTailNode]; // 删除时间过时的数据
if (node) [holder addObject:node];
} else {
finish = YES;
}
pthread_mutex_unlock(&_lock);
} else {
usleep(10 * 1000); //10 ms
}
}
...
}
监听内存警告通知/进入后台通知
自动清空内存缓存,调用 removeAllObjects
方法
增删查改
所有操作都需要进行线程安全控制:加锁
增/改
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38- (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];
...
}
pthread_mutex_unlock(&_lock);
}删
1
2
3
4
5
6
7
8
9
10
11
12
13/// 根据key进行删除
- (void)removeObjectForKey:(id)key {
...
pthread_mutex_lock(&_lock);
_YYLinkedMapNode *node = CFDictionaryGetValue(_lru->_dic, (__bridge const void *)(key));
if (node) {
[_lru removeNode:node];
...
}
pthread_mutex_unlock(&_lock);
}
/// 删除所有
- (void)removeAllObjects;查
1
2
3
4/// 是否包含某个数据
- (BOOL)containsObjectForKey:(id)key;
/// 获取缓存数据: 1.更新节点的时间戳,2.并将节点移到头结点
- (id)objectForKey:(id)key;
总结
- YYMemoryCache 操作了内存缓存, 相较于磁盘缓存需要进行I/O操作, 在性能上快很多, 因此YYCache访问缓存时, 优先用的是YYMemoryCache。
- YYMemoryCache 就是操作双向链表和哈希字典,使用 LRU 算法操作数据