本文来自FlyOceanFish的投稿
开头
俗话说三人行必有我师,所以我们得多向他人学习。于是选了YYCache这一大神开源的缓存框架进行代码的研究和学习。作者这里仅仅做一个自身的总结和将一些感悟写出了。
思路简析
YYCache包含两部分:内存缓存(YYMemoryCache)和物理缓存(YYDiskCache)。通过YYCache对两者的封装,提供统一接口,完成了对缓存的实现。
存数据
- (void)setObject:(id)object forKey:(NSString *)key { [_memoryCache setObject:object forKey:key]; [_diskCache setObject:object forKey:key]; }
首先是将数据存入缓存,同时在物理硬盘上缓存一份
取数据
- (id)objectForKey:(NSString *)key { id object = [_memoryCache objectForKey:key]; if (!object) { object = [_diskCache objectForKey:key]; if (object) { [_memoryCache setObject:object forKey:key]; } } return object; }
取数据首先是从内存取(因为内存中读取是比物理硬盘读取速度快很多的),如果取不到(由于
其实大部分的的缓存实现比如SDImageCache等实现思路都是这样的,真正的差别主要是内存缓存和物理硬盘缓存具体实现细节上的差别
YYMemoryCache
YYMemoryCache采用的是双向链表和Dictionary结合的技术,实现了增删时间复杂度是O(1)。双向链表通过MRU和LRU的思想进行删除和增加
- (void)setObject:(id)object forKey:(id)key withCost:(NSUInteger)cost { if (!key) return; if (!object) { [self removeObjectForKey:key]; return; } pthread_mutex_lock(&_lock);//使用pthread互斥锁,原先是OSSpinLock,后来作者更换了。不过可能由于后期好久没维护了,所以没使用苹果的os_unfair_lock替换品 _YYLinkedMapNode *node = CFDictionaryGetValue(_lru->_dic, (__bridge const void *)(key));//之前存储过的话就取出来并将其放到双向链表的第一个。MRU 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];//如果超过数量限制,删除链表的最后一个。LRU if (_lru->_releaseAsynchronously) { dispatch_queue_t queue = _lru->_releaseOnMainThread ? dispatch_get_main_queue() : YYMemoryCacheGetReleaseQueue(); dispatch_async(queue, ^{ [node class]; //随便调一下方法,目的就是将对象的释放放到子线程里边 }); } else if (_lru->_releaseOnMainThread && !pthread_main_np()) { dispatch_async(dispatch_get_main_queue(), ^{ [node class]; //hold and release in queue }); } } pthread_mutex_unlock(&_lock); }
YYDiskCache
YYDiskCache物理缓存采用的是文件和SQLite结合的方式,作者是低于20k存储到SQLite,超过20k存储文件。关于20k的阈值,本人有点疑惑。 SQLite 官网的描述官网最后的结论写的是100k,20k有可能YYCache的作者是通过观察实现数据做出的结论吧。 物理缓存使用的锁是信号,主要是信号不用消耗CPU
- (void)setObject:(id)object forKey:(NSString *)key { if (!key) return; if (!object) { [self removeObjectForKey:key]; return; } NSData *extendedData = [YYDiskCache getExtendedDataFromObject:object]; NSData *value = nil; if (_customArchiveBlock) { value = _customArchiveBlock(object); } else { @try { value = [NSKeyedArchiver archivedDataWithRootObject:object]; } @catch (NSException *exception) { // nothing to do... } } if (!value) return; NSString *filename = nil; if (_kv.type != YYKVStorageTypeSQLite) {//YYCache默认使用的是混合模式 if (value.length > _inlineThreshold) {//是否大于阈值20k,大于的话使用文件存储 filename = [self _filenameForKey:key]; } } Lock();//信号锁 [_kv saveItemWithKey:key value:value filename:filename extendedData:extendedData]; Unlock(); }
感悟
本文没有很详细的解析源码,主要提取了一些重要片段进行了解析。因为YYCache的整体源码思路非常清晰,代码很整洁,所以读起来一点不费事,这里主要讲述思路,详细的完整实现建议亲自去读源码。非常佩服YYKit大神的工匠精神,为了写一个YYCache框架其实是调研了许多的第三方框架和资料。
我的博客