关于YYCache的简单总结。


阅读YYCache源码有3遍了,自我感觉“需要理解”的部分掌握的差不多了,做个记录。

一、接口

API 类似字典,比较容易理解。

1.初始化

1
2
3
4
5
6
7
8
9
@property (copy, readonly) NSString *name;
@property (strong, readonly) YYMemoryCache *memoryCache;
@property (strong, readonly) YYDiskCache *diskCache;
- (nullable instancetype)initWithName:(NSString *)name;
- (nullable instancetype)initWithPath:(NSString *)path NS_DESIGNATED_INITIALIZER;
+ (nullable instancetype)cacheWithName:(NSString *)name;
+ (nullable instancetype)cacheWithPath:(NSString *)path;
- (instancetype)init UNAVAILABLE_ATTRIBUTE;
+ (instancetype)new UNAVAILABLE_ATTRIBUTE;

2.是否存在

1
2
- (BOOL)containsObjectForKey:(NSString *)key;
- (void)containsObjectForKey:(NSString *)key withBlock:(nullable void(^)(NSString *key, BOOL contains))block;

3.查询

1
2
- (nullable id<NSCoding>)objectForKey:(NSString *)key;
- (void)objectForKey:(NSString *)key withBlock:(nullable void(^)(NSString *key, id<NSCoding> object))block;

4.设值

1
2
- (void)setObject:(nullable id<NSCoding>)object forKey:(NSString *)key;
- (void)setObject:(nullable id<NSCoding>)object forKey:(NSString *)key withBlock:(nullable void(^)(void))block;

5.移除

1
2
3
4
5
- (void)removeObjectForKey:(NSString *)key;
- (void)removeObjectForKey:(NSString *)key withBlock:(nullable void(^)(NSString *key))block;
- (void)removeAllObjects;
- (void)removeAllObjectsWithBlock:(void(^)(void))block;
- (void)removeAllObjectsWithProgressBlock:(nullable void(^)(int removedCount, int totalCount))progress endBlock:(nullable void(^)(BOOL error))end;

二、实现

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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
@implementation YYCache

- (instancetype) init {
NSLog(@"Use \"initWithName\" or \"initWithPath\" to create YYCache instance.");
return [self initWithPath:@""];
}

- (instancetype)initWithName:(NSString *)name {
if (name.length == 0) return nil;
NSString *cacheFolder = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) firstObject];
NSString *path = [cacheFolder stringByAppendingPathComponent:name];
return [self initWithPath:path];
}

- (instancetype)initWithPath:(NSString *)path {
if (path.length == 0) return nil;
YYDiskCache *diskCache = [[YYDiskCache alloc] initWithPath:path];
if (!diskCache) return nil;
NSString *name = [path lastPathComponent];
YYMemoryCache *memoryCache = [YYMemoryCache new];
memoryCache.name = name;

self = [super init];
_name = name;
_diskCache = diskCache;
_memoryCache = memoryCache;
return self;
}

+ (instancetype)cacheWithName:(NSString *)name {
return [[self alloc] initWithName:name];
}

+ (instancetype)cacheWithPath:(NSString *)path {
return [[self alloc] initWithPath:path];
}

- (BOOL)containsObjectForKey:(NSString *)key {
return [_memoryCache containsObjectForKey:key] || [_diskCache containsObjectForKey:key];
}

- (void)containsObjectForKey:(NSString *)key withBlock:(void (^)(NSString *key, BOOL contains))block {
if (!block) return;

if ([_memoryCache containsObjectForKey:key]) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
block(key, YES);
});
} else {
[_diskCache containsObjectForKey:key withBlock:block];
}
}

- (id<NSCoding>)objectForKey:(NSString *)key {
id<NSCoding> object = [_memoryCache objectForKey:key];
if (!object) {
object = [_diskCache objectForKey:key];
if (object) {
[_memoryCache setObject:object forKey:key];
}
}
return object;
}

- (void)objectForKey:(NSString *)key withBlock:(void (^)(NSString *key, id<NSCoding> object))block {
if (!block) return;
id<NSCoding> object = [_memoryCache objectForKey:key];
if (object) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
block(key, object);
});
} else {
[_diskCache objectForKey:key withBlock:^(NSString *key, id<NSCoding> object) {
if (object && ![_memoryCache objectForKey:key]) {
[_memoryCache setObject:object forKey:key];
}
block(key, object);
}];
}
}

- (void)setObject:(id<NSCoding>)object forKey:(NSString *)key {
[_memoryCache setObject:object forKey:key];
[_diskCache setObject:object forKey:key];
}

- (void)setObject:(id<NSCoding>)object forKey:(NSString *)key withBlock:(void (^)(void))block {
[_memoryCache setObject:object forKey:key];
[_diskCache setObject:object forKey:key withBlock:block];
}

- (void)removeObjectForKey:(NSString *)key {
[_memoryCache removeObjectForKey:key];
[_diskCache removeObjectForKey:key];
}

- (void)removeObjectForKey:(NSString *)key withBlock:(void (^)(NSString *key))block {
[_memoryCache removeObjectForKey:key];
[_diskCache removeObjectForKey:key withBlock:block];
}

- (void)removeAllObjects {
[_memoryCache removeAllObjects];
[_diskCache removeAllObjects];
}

- (void)removeAllObjectsWithBlock:(void(^)(void))block {
[_memoryCache removeAllObjects];
[_diskCache removeAllObjectsWithBlock:block];
}

- (void)removeAllObjectsWithProgressBlock:(void(^)(int removedCount, int totalCount))progress
endBlock:(void(^)(BOOL error))end {
[_memoryCache removeAllObjects];
[_diskCache removeAllObjectsWithProgressBlock:progress endBlock:end];

}

- (NSString *)description {
if (_name) return [NSString stringWithFormat:@"<%@: %p> (%@)", self.class, self, _name];
else return [NSString stringWithFormat:@"<%@: %p>", self.class, self];
}

@end

整体来看还是比较容易理解的,YYCache 整合了内存缓存和磁盘缓存,主要做了这些事:

  1. 初始化YYCache实例,要求缓存路径要合理。
  2. 查询缓存对象先从内存缓存中查找,如果没有,再从磁盘缓存中查找。当磁盘缓存中有而内存缓存中没有的时候,把取得的缓存对象保存在内存缓存中。
  3. 缓存对象的存取和移除,内存缓存和磁盘缓存两者保持同步。

1.YYMemoryCache

基本属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@property (nullable, copy) NSString *name;
@property (readonly) NSUInteger totalCount;
@property (readonly) NSUInteger totalCost;


#pragma mark - Limit
@property NSUInteger countLimit;
@property NSUInteger costLimit;
@property NSTimeInterval ageLimit;
@property NSTimeInterval autoTrimInterval;
@property BOOL shouldRemoveAllObjectsOnMemoryWarning;
@property BOOL shouldRemoveAllObjectsWhenEnteringBackground;
@property (nullable, copy) void(^didReceiveMemoryWarningBlock)(YYMemoryCache *cache);
@property (nullable, copy) void(^didEnterBackgroundBlock)(YYMemoryCache *cache);
@property BOOL releaseOnMainThread;
@property BOOL releaseAsynchronously;

类似于NSCache,提供包括缓存数量、缓存花费、缓存时间的管理。除此之外,当接收到内存警告时(shouldRemoveAllObjectsOnMemoryWarning)或者 App 进入到后台时(shouldRemoveAllObjectsWhenEnteringBackground)可以选择释放缓存对象。而对缓存对象释放也可以进行控制,比如可以选择在主线程释放(releaseOnMainThread)或者异步释放(releaseAsynchronously)

存取接口

1
2
3
4
5
6
7
8
9
10
- (BOOL)containsObjectForKey:(id)key;
- (nullable id)objectForKey:(id)key;
- (void)setObject:(nullable id)object forKey:(id)key;
- (void)setObject:(nullable id)object forKey:(id)key withCost:(NSUInteger)cost;
- (void)removeObjectForKey:(id)key;
- (void)removeAllObjects;

- (void)trimToCount:(NSUInteger)count;
- (void)trimToCost:(NSUInteger)cost;
- (void)trimToAge:(NSTimeInterval)age;

除了基本的存取方法之外,YYMemoryCache 还暴露了三个移除缓存对象的方法。
trimToCount:根据限制的数量(countLimit)进行移除缓存对象操作,直到满足数量限制要求。
trimToCost:根据限制的花费(costLimit)进行移除缓存对象操作,直到满足花费限制要求。
trimToAge:根据缓存对象的过期时间(ageLimit)进行移除缓存对象操作,直到满足要求。

具体实现

在具体实现中,作者考虑了以下要求:

  1. 缓存对象的存取。要保证效率就要求存取时间复杂度最好是O(1)。
  2. 缓存对象的移除。要保证能按照 cost、age、count 等条件对所有不符合要求的对象进行移除。
  3. 线程安全。需要加锁。
    第一条:要求时间复杂度是 O(1),那就可以采用哈希表、字典等。作者使用了字典,且是效率更高的CFMutableDictionaryRef
    第二条:记录 cost、age、count等属性,肯定需要对缓存对象进行包装。再者需要考虑LRU(Least Recently Used),就需要保证顺序。而要使得字典中的对象(value)有顺序,必须有一个指向其他对象的指针(属性)。可以使用双向链表包装缓存对象。
    第三条:存取的线程安全,使用GCD线程锁。

结点

1
2
3
4
5
6
7
8
9
10
@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

链表的结点中保存着key 和 value,这是对缓存对象的包装。_cost和_time记录着缓存对象的花费和过期时间。而使用__unsafe_unretained修饰的_prev和_next则分别指向前一个对象和后一个对象。由于这些结点已经被字典持有了,所以直接使用__unsafe_unretained修饰,不必再增加它的引用计数,提高效率。这一点与隐式参数self很像,self其实也是使用__unsafe_unretained修饰的。

链表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@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;
}

- (void)insertNodeAtHead:(_YYLinkedMapNode *)node;
- (void)bringNodeToHead:(_YYLinkedMapNode *)node;
- (void)removeNode:(_YYLinkedMapNode *)node;
- (_YYLinkedMapNode *)removeTailNode;
- (void)removeAll;
@end

一个 YYMemoryCache 对象有一个链表。这个链表使用_dic保存着所有包装好的缓存对象(_YYLinkedMapNode),记录着总的花费(_totalCost)和总的数量(_totalCount)。当然,还使用_head指着链表的头指针,使用_tail指着链表的尾指针。_releaseOnMainThread_releaseAsynchronously用于设置对缓存对象释放操作的选项:主线程释放或者异步释放。

_YYLinkedMap暴露出的五个方法很清晰地表明它的作用:每次当缓存取到某个对象时,把它置在头结点的位置。这样随着时间的推移,很轻松地使得那些不经常使用的对象处在链表的后端,经常使用的对象处在链表的前端,这样就实现了 LRU

操作

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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
- (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;
}
}

- (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;
}

- (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;
}

- (_YYLinkedMapNode *)removeTailNode {
if (!_tail) return nil;
_YYLinkedMapNode *tail = _tail;
CFDictionaryRemoveValue(_dic, (__bridge const void *)(_tail->_key));
_totalCost -= _tail->_cost;
_totalCount--;
if (_head == _tail) {
_head = _tail = nil;
} else {
_tail = _tail->_prev;
_tail->_next = nil;
}
return tail;
}

对结点的插入、移除、调整位置,是数据结构的基础操作。令人怀念!

_releaseOnMainThread_releaseAsynchronously这两个选项的实现也很简单。作者自己维护了一个队列:

1
2
3
static inline dispatch_queue_t YYMemoryCacheGetReleaseQueue() {
return dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);
}

移除的时候有所判断:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
if (CFDictionaryGetCount(_dic) > 0) {
CFMutableDictionaryRef holder = _dic;
_dic = CFDictionaryCreateMutable(CFAllocatorGetDefault(), 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);

if (_releaseAsynchronously) {
dispatch_queue_t queue = _releaseOnMainThread ? dispatch_get_main_queue() : YYMemoryCacheGetReleaseQueue();
dispatch_async(queue, ^{
CFRelease(holder); // hold and release in specified queue
});
} else if (_releaseOnMainThread && !pthread_main_np()) {
dispatch_async(dispatch_get_main_queue(), ^{
CFRelease(holder); // hold and release in specified queue
});
} else {
CFRelease(holder);
}
}

根据线程、是否异步决定具体的逻辑,把release操作放到相应线程中。这里使用一个临时变量holder,很是巧妙。

核心操作

1
2
3
4
5
@implementation YYMemoryCache {
pthread_mutex_t _lock;
_YYLinkedMap *_lru;
dispatch_queue_t _queue;
}

从这里可以看出YYMemoryCache使用pthread_mutex_t保证线程安全。

最关键的还是对无效缓存对象的释放,以 count 为例,当缓存的对象数量超过了 count 限制,就需要对链表后端不常使用的缓存对象进行移除操作,直到满足 count 限制。

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
- (void)_trimToCount:(NSUInteger)countLimit {
BOOL finish = NO;
pthread_mutex_lock(&_lock);
if (countLimit == 0) {
[_lru removeAll];
finish = YES;
} else if (_lru->_totalCount <= countLimit) {
finish = YES;
}
pthread_mutex_unlock(&_lock);
if (finish) return;

NSMutableArray *holder = [NSMutableArray new];
while (!finish) {
if (pthread_mutex_trylock(&_lock) == 0) {
if (_lru->_totalCount > countLimit) {
_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
});
}
}

这个方法做了什么?

  1. 使用pthread_mutex_lock(&_lock);pthread_mutex_unlock(&_lock);对操作加锁解锁来保证线程安全。
  2. 对参数 countLimit 判断:
    如果countLimit为0,也就是说缓存数量限制为0,那移除所有缓存对象就是了。如果当前所缓存的对象数量小于countLimit,那说明满足数量限制要求,就不需要移除操作了。
  3. 作者创建一个可变字典holder,当不满足countLimit限制要求的时候,对链表尾结点进行移除操作,并把这个尾结点添加到holder中持有。当然,这里有加锁操作。这个 while 循环结束,所有多余的缓存对象就在holder中了。
    4.对holder中所有元素进行 release 操作。
    其他的如:
1
2
- (void)_trimToAge:(NSTimeInterval)ageLimit; 
- (void)_trimToCost:(NSUInteger)costLimit

操作同理。

内存警告

内存警告语 App 进入后台时释放缓存对象的操作,作者接受了系统通知,直接处理即可。

1
2
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_appDidReceiveMemoryWarningNotification) name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_appDidEnterBackgroundNotification) name:UIApplicationDidEnterBackgroundNotification object:nil];

存取实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
- (BOOL)containsObjectForKey:(id)key {
if (!key) return NO;
pthread_mutex_lock(&_lock);
BOOL contains = CFDictionaryContainsKey(_lru->_dic, (__bridge const void *)(key));
pthread_mutex_unlock(&_lock);
return contains;
}

- (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;
}

- (void)setObject:(id)object forKey:(id)key {
[self setObject:object forKey:key withCost:0];
}

对缓存对象的读取自然是根据 key 读取到字典中对应的 value,这个 value 是个结点(_YYLinkedMapNode),再取出这个结点的value 属性,便是最原始的缓存对象了:node->_value

2.关键点

1.字典CFMutableDictionaryRef的使用

1
2
3
4
5
6
7
8
声明: CFMutableDictionaryRef _dic;
创建: _dic = CFDictionaryCreateMutable(CFAllocatorGetDefault(), 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
设值: CFDictionarySetValue(_dic, (__bridge const void *)(node->_key), (__bridge const void *)(node));
取值: CFDictionaryGetValue(_lru->_dic, (__bridge const void *)(key));
移除: CFDictionaryRemoveValue(_dic, (__bridge const void *)(node->_key));
获取数量: CFDictionaryGetCount(_dic);
是否存在: CFDictionaryContainsKey(_lru->_dic, (__bridge const void *)(key));
释放: CFRelease(_dic);

2.容器中对象销毁控制

“对象的销毁虽然消耗资源不多,但累积起来也是不容忽视的。通常当容器类持有大量对象时,其销毁时的资源消耗就非常明显。同样的,如果对象可以放到后台线程去释放,那就挪到后台线程去…”

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 数组中的所有元素在子线程释放
{
NSArray *holder = tmp;
tmp = [NSMutableArray array];
if (holder.count > 0) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[holder count];
});
}
}


// 数组中的某个元素在子线程释放
{
id obj = tmp[2];
NSMutableArray *holder = [NSMutableArray array];
[holder addObject:obj];
[tmp removeObject:obj];
if (holder.count > 0) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[holder count];
});
}
}

3.线程安全的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 取值线程安全
{
pthread_mutex_lock(&_lock);
BOOL releaseAsynchronously = _lru->_releaseAsynchronously;
pthread_mutex_unlock(&_lock);
return releaseAsynchronously;
}

// 设值线程安全
{
pthread_mutex_lock(&_lock);
_lru->_releaseAsynchronously = releaseAsynchronously;
pthread_mutex_unlock(&_lock);
}

4.pthread_mutex_lock使用

1
2
3
4
5
6
7
8
9
10
11
声明锁: pthread_mutex_t _lock;
创建锁: pthread_mutex_init(&_lock, NULL);
加锁: pthread_mutex_lock(&_lock);
解锁: pthread_mutex_unlock(&_lock);
尝试加锁:
if (pthread_mutex_trylock(&_lock) == 0) {
pthread_mutex_unlock(&_lock);
} else {
usleep(10 * 1000); //10 ms
}
销毁锁: pthread_mutex_destroy(&_lock);

5.if-else 单句

1
2
if (_name) return nil;
else return nil;

3.YYDiskCache

YYDiskCache主要调用了YYKVStorage的接口,并提供对外 API。

五个函数

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
39
40
41
42
43
44
45
46
47
48
49
/// Free disk space in bytes.
static int64_t _YYDiskSpaceFree() {
NSError *error = nil;
NSDictionary *attrs = [[NSFileManager defaultManager] attributesOfFileSystemForPath:NSHomeDirectory() error:&error];
if (error) return -1;
int64_t space = [[attrs objectForKey:NSFileSystemFreeSize] longLongValue];
if (space < 0) space = -1;
return space;
}

/// String's md5 hash.
static NSString *_YYNSStringMD5(NSString *string) {
if (!string) return nil;
NSData *data = [string dataUsingEncoding:NSUTF8StringEncoding];
unsigned char result[CC_MD5_DIGEST_LENGTH];
CC_MD5(data.bytes, (CC_LONG)data.length, result);
return [NSString stringWithFormat:
@"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
result[0], result[1], result[2], result[3],
result[4], result[5], result[6], result[7],
result[8], result[9], result[10], result[11],
result[12], result[13], result[14], result[15]
];
}

static void _YYDiskCacheInitGlobal() {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_globalInstancesLock = dispatch_semaphore_create(1);
_globalInstances = [[NSMapTable alloc] initWithKeyOptions:NSPointerFunctionsStrongMemory valueOptions:NSPointerFunctionsWeakMemory capacity:0];
});
}

static YYDiskCache *_YYDiskCacheGetGlobal(NSString *path) {
if (path.length == 0) return nil;
_YYDiskCacheInitGlobal();
dispatch_semaphore_wait(_globalInstancesLock, DISPATCH_TIME_FOREVER);
id cache = [_globalInstances objectForKey:path];
dispatch_semaphore_signal(_globalInstancesLock);
return cache;
}

static void _YYDiskCacheSetGlobal(YYDiskCache *cache) {
if (cache.path.length == 0) return;
_YYDiskCacheInitGlobal();
dispatch_semaphore_wait(_globalInstancesLock, DISPATCH_TIME_FOREVER);
[_globalInstances setObject:cache forKey:cache.path];
dispatch_semaphore_signal(_globalInstancesLock);
}

_YYDiskSpaceFree()提供剩余磁盘空间的查询。
_YYNSStringMD5(NSString *string)提供字符串转 md5。

_YYDiskCacheInitGlobal()用于初始化一个强-弱的NSMapTable,保存YYDiskCache对象。
_YYDiskCacheGetGlobal(NSString *path)用于根据路径 path 获取对应的YYDiskCache对象。
_YYDiskCacheSetGlobal(YYDiskCache *cache)用于根据路径 path 在NSMapTable保存一个YYDiskCache对象。

主要实现

YYDiskCache的功能比如移除过期的对象、移除超过数量限制的对象等,主要通过YYKVStorage实现。
作者把保存类型分为三种:

1
2
3
4
5
6
7
8
typedef NS_ENUM(NSUInteger, YYKVStorageType) {
/// file system.
YYKVStorageTypeFile = 0,
/// in sqlite.
YYKVStorageTypeSQLite = 1,
/// based on your choice.
YYKVStorageTypeMixed = 2,
};

作者指明了原因:Typically, write data to sqlite is faster than extern file, but
reading performance is dependent on data size. In my test (on iPhone 6 64G),
read data from extern file is faster than from sqlite when the data is larger
than 20KB.

  • If you want to store large number of small datas (such as contacts cache),
    use YYKVStorageTypeSQLite to get better performance.
  • If you want to store large files (such as image cache),
    use YYKVStorageTypeFile to get better performance.
  • You can use YYKVStorageTypeMixed and choice your storage type for each item.

    20kb 以下的持久化,放到文件中。 20kb 以上的持久化,放到数据库sqlite中。也可以选择混合存储。

4.YYKVStorage

YYKVStorage没有阅读。乍一看是许多琐碎的 SQL 操作和文件操作,没有纳入阅读计划。

三、小结

YYCache属于小家碧玉型的开源作品,代码量不是很多,但是很精美。LRU配合双向链表也是很经典的算法题。我觉得最好玩的是容器元素的异步释放逻辑,以前确实没见过这种写法,作者也给出了解释,很值得学习借鉴。