在上一篇文章 中,我们对YYCache
的初始化操作了做了简单分析,具体代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 - (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 ; }
本篇文章我们介绍一下 YYMemoryCache
磁盘缓存的实现
YYMemoryCache 简介 先来看一下官方介绍(可在源码中查阅):
1 2 3 4 5 6 7 8 9 10 11 `YYMemoryCache`是一种快速的`内存缓存`,用于存储`键值对`。 与NSDictionary不同,key是retain的,而不是copy(必须符合NSCopying协议)的。 API和性能类似于`NSCache`,所有方法都是`线程安全`的。 YYMemoryCache对象与NSCache在以下几个方面有所不同: - 它使用LRU(最近最少使用)删除对象;NSCache驱逐的方法 是不确定的。 - 可通过cost、count、age进行控制;NSCache的限制并不精确。 - 可配置为当收到内存警告或app进入后台时自动清除对象。 `YYMemoryCache`中读取操作相关的api的时间复杂度O(1)。
YYMemoryCache 源码总览
YYMemoryCache 初始化 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 @implementation YYMemoryCache { pthread_mutex_t _lock; _YYLinkedMap *_lru; dispatch_queue_t _queue; } - (instancetype )init { self = super .init; pthread_mutex_init(&_lock, NULL ); _lru = [_YYLinkedMap new]; _queue = dispatch_queue_create("com.ibireme.cache.memory" , DISPATCH_QUEUE_SERIAL); _countLimit = NSUIntegerMax ; _costLimit = NSUIntegerMax ; _ageLimit = DBL_MAX; _autoTrimInterval = 5.0 ; _shouldRemoveAllObjectsOnMemoryWarning = YES ; _shouldRemoveAllObjectsWhenEnteringBackground = YES ; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector (_appDidReceiveMemoryWarningNotification) name:UIApplicationDidReceiveMemoryWarningNotification object:nil ]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector (_appDidEnterBackgroundNotification) name:UIApplicationDidEnterBackgroundNotification object:nil ]; [self _trimRecursively]; return self ; }
YYMemoryCache 定时探测机制
YYMemoryCache
内部有一个定时任务,用来检查缓存是否到达极限,如果达到极限,就开始删除数据
在YYMemoryCache
初始化的时候,初始化了 _autoTrimInterval = 5.0;
即:定时周期默认为5秒。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 - (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]; }); }
YYMemoryCache
定时探测机制:
默认5秒一次探测;_autoTrimInterval = 5.0;
使用dispatch_after
+ 递归调用
实现定时探测;
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0)
:低优先级的全局队列
_YYLinkedMap & _YYLinkedMapNode _YYLinkedMap
& _YYLinkedMapNode
为内部私有类;
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 @interface _YYLinkedMap : NSObject { @package CFMutableDictionaryRef _dic; NSUInteger _totalCost; NSUInteger _totalCount; _YYLinkedMapNode *_head; _YYLinkedMapNode *_tail; BOOL _releaseOnMainThread; BOOL _releaseAsynchronously; } - (void )insertNodeAtHead:(_YYLinkedMapNode *)node; - (void )bringNodeToHead:(_YYLinkedMapNode *)node; - (void )removeNode:(_YYLinkedMapNode *)node; - (_YYLinkedMapNode *)removeTailNode; - (void )removeAll; @end
1 2 3 4 5 6 7 8 9 10 @interface _YYLinkedMapNode : NSObject { @package __unsafe_unretained _YYLinkedMapNode *_prev; __unsafe_unretained _YYLinkedMapNode *_next; id _key; id _value; NSUInteger _cost; NSTimeInterval _time; } @end
由于 _YYLinkedMapNode
对象已经存在于_dic
内,且被retain
,_YYLinkedMapNode
对象内部的_prev
和 _next
使用的是__unsafe_unretained
;
YYMemoryCache LRU 从以下四个
API来讲解 YYMemoryCache
LRU
;
1 2 3 4 5 6 7 - (void )trimToCount:(NSUInteger )count; - (_YYLinkedMapNode *)removeTailNode; - (nullable id )objectForKey:(id )key; - (void )bringNodeToHead:(_YYLinkedMapNode *)node;
trimToCount
内部会调用removeTailNode
, 下文分析
1 2 3 4 5 6 7 8 - (void )trimToCount:(NSUInteger )count { if (count == 0 ) { [self removeAllObjects]; return ; } [self _trimToCount: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 34 35 36 - (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 ); } } if (holder.count) { dispatch_queue_t queue = _lru->_releaseOnMainThread ? dispatch_get_main_queue() : YYMemoryCacheGetReleaseQueue(); dispatch_async (queue, ^{ [holder count]; }); } }
异步释放对象
YYMemoryCacheGetReleaseQueue()
1 2 3 static inline dispatch_queue_t YYMemoryCacheGetReleaseQueue() { return dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0 ); }
使用了一个低优先级(DISPATCH_QUEUE_PRIORITY_LOW)
的 全局并发队列
;
使用 static
inline
关键字已提高执行效率;
static inline 静态内联函数
1 2 3 4 5 通过声明一个内联函数,可以指示GCC更快地调用该函数。 GCC可以做到这一点的一种方法是将该函数的代码集成到其调用者的代码中。 这样可以消除函数调用的开销,从而加快执行速度。 此外,如果任何实际参数值是恒定的,则它们的已知值可能会在编译时进行简化,因此不必包括所有内联函数的代码。 对代码大小的影响难以预测。根据具体情况,使用函数内联可以使目标代码更大或更小。
引用自:An Inline Function is As Fast As a Macro
在保持界面流畅的技巧
文章中作者提到:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 NSArray *tmp = self .array;self .array = nil ;dispatch_async (queue, ^{ [tmp class ]; });
removeTailNode 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 - (_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; }
objectForKey
内部会调用bringNodeToHead
, 下文分析
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 - (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 ; }
bringNodeToHead 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 - (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; }
YYMemoryCache读取操作时间复杂度O(1) 在官方的文档中,我们得知 YYMemoryCache
读取操作相关api的时间复杂度为 O(1)
,那么是如何做到的呢?
1 2 3 4 5 6 7 8 9 10 11 12 @interface _YYLinkedMap : NSObject { @package CFMutableDictionaryRef _dic; NSUInteger _totalCost; NSUInteger _totalCount; _YYLinkedMapNode *_head; _YYLinkedMapNode *_tail; BOOL _releaseOnMainThread; BOOL _releaseAsynchronously; }
其实就是这个 CFMutableDictionaryRef
类型的 _dic
;CFMutableDictionaryRef
本质是一个 哈希结构
;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 - (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 ; }