1.应该先有一个设计方案
可以先看看作者的博客关于YYMemoryCache的设计思路传送门,大概可以知道,NSCache还是挺好的,读取很快,写入的时候由于key的原因稍微慢点,但是对缓存的totalCostLimit自动清除缓存却是不可控制。那么,如果对NSCache进行改造,或者站在NSCache的设计思路上,进一步提供比较快的写入速度,提供自动清除的可控性,应该会更好一点。
那么如何选材呢?从两个方面,读写速度方面,可控方面。
NSDictionary有NSCache一样需要的的key-value对,读取速度非常快。对于控制清除缓存方面可以写入由时间先后来决定,写入value的时候带上时间并放到一个队列的前面,读取的时候把该value放到最前面(MRU),清除的时候从最后面开始(LRU)。
那么这个队列应该由什么来控制呢?
我们先看看数组,内存空间连续,读取速度快,但是修改速度慢,但是现在我们需要的是修改速度比较快的就行了,读取上已经有了NSDictionary。链表是修改速度非常快的队列,存储上内存空间不连续。虽然读取的时候慢点,但是我们不需要用它来读取啊,而且链表也能停供头尾的操作,很符合LRU。
所以选定材料是,NSDictionary+双线链表+LRU+(NSCache的线程安全+内存警告处理+转入后台处理+实时limit处理)。
2.如何来封装
应该有一个类来提供外部的接口,类似于NSCache,暂且把这个叫交互层。
应该有一个内部的类来响应交互层的command,提供数据的读写,删除,也就是双向链表和字典的操作,暂且把这个叫做处理层。
应该还有一个链表节点类。组件层。
a.先来看看对外交互层这个类YYMemoryCache。
先了解下YYMemoryCache.h
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 |
#pragma mark - Attribute @property (nullable, copy) NSString *name;//没什么用 @property (readonly) NSUInteger totalCount;//缓存的object数量 @property (readonly) NSUInteger totalCost;//缓存cost总数 #pragma mark - Limit @property NSUInteger countLimit;//设置cache缓存object数量的最大值,超过就会采用LRU来清除后面的,直到的totalCount ageLimit,都会被清除,处理方式同countLimit。 @property NSTimeInterval autoTrimInterval;//定期清理缓存时间,默认为5秒。清理规则是上面的三个limit。 @property BOOL shouldRemoveAllObjectsOnMemoryWarning;//当接收到来自系统的内存警告时,是否要清除所有缓存,默认是 YES。建议使用默认。 @property BOOL shouldRemoveAllObjectsWhenEnteringBackground;//当进入后台的时候是否要清除所有缓存,默认是 YES。建议使用默认。 @property (nullable, copy) void(^didReceiveMemoryWarningBlock)(YYMemoryCache *cache);//内存警告时的block @property (nullable, copy) void(^didEnterBackgroundBlock)(YYMemoryCache *cache);//进入后台时的block @property BOOL releaseOnMainThread;//是否在主线程释放节点内存,默认为NO,也就是默认在后台释放内存(hold ,than release)。 @property BOOL releaseAsynchronously;//和releaseOnMainThread相反。 #pragma mark - Access Methods - (BOOL)containsObjectForKey:(id)key;//是否存储了某个key - (nullable id)objectForKey:(id)key;//获取object - (void)setObject:(nullable id)object forKey:(id)key;//写入object - (void)setObject:(nullable id)object forKey:(id)key withCost:(NSUInteger)cost;//写入object,并且设置每个object的cost - (void)removeObjectForKey:(id)key;//删除某个object - (void)removeAllObjects;//清空缓存 #pragma mark - Trim 根据limit规则来截取移除 - (void)trimToCount:(NSUInteger)count;//根据object数量来移除 - (void)trimToCost:(NSUInteger)cost;//根据cost数量来移除。 - (void)trimToAge:(NSTimeInterval)age;//根据存活时间来移除。 |
再来看看YYMemoryCache实现的私有属性。
1 2 3 4 5 |
@implementation YYMemoryCache { pthread_mutex_t _lock;//互斥锁,保证线程安全,对于所有的属性和方法 _YYLinkedMap *_lru;//处理层类。处理链表操作 dispatch_queue_t _queue;//串联队列, } |
b.处理层_YYLinkedMap(链表操作)
//操作时需要主要双链表的两个链都要连好
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
@interface _YYLinkedMap : NSObject { @package CFMutableDictionaryRef _dic; // 用来存储节点 NSUInteger _totalCost;// NSUInteger _totalCount;// _YYLinkedMapNode *_head; // MRU, 链表头节点 _YYLinkedMapNode *_tail; // LRU, 链表尾节点。链表会在这个_YYLinkedMap形成。 BOOL _releaseOnMainThread; BOOL _releaseAsynchronously; } - (void)insertNodeAtHead:(_YYLinkedMapNode *)node;//在链表的head之前添加节点,如果head == nil,则head = node。也就是Cache写入一个object。 - (void)bringNodeToHead:(_YYLinkedMapNode *)node;//把node移到head。也就是Cache读取一个object,或则从新设置key_vale的时候。 - (void)removeNode:(_YYLinkedMapNode *)node;//删除一个节点,也就时Cache移除一个object - (_YYLinkedMapNode *)removeTailNode; - (_YYLinkedMapNode *)removeTailNode;//根据那三个limit规则来移除节点 - (void)removeAll;//清空所有节点 |
c.组件层_YYLinkedMapNode