区别于NSDictionary, YYMemoryCache对key是retain而不是copy操做(YYMemoryCache的key能够是任何对象的缘由, 内部是使用CFMutableDictionaryRef实现的), 提供的API和NSCache相似, 而且YYMemoryCache的全部方法都是安全的.node
YYMemoryCache相比NSCache的特色:算法
YYMemoryCache中使用一个双向链表和字典进行增删改查操做的:数组
YYLinkedMapNode能够理解为链表中的结点. YYMemoryCache中是将传入的value封装成node存储到缓存中的.缓存
@interface _YYLinkedMapNode : NSObject {
@package
__unsafe_unretained _YYLinkedMapNode *_prev; // retained by dic
__unsafe_unretained _YYLinkedMapNode *_next; // retained by dic
id _key; // key
id _value; // value
NSUInteger _cost; // 结点的开销, 在内存中占用的字节数
NSTimeInterval _time; // 操做结点的时间
}
@end
@implementation _YYLinkedMapNode
@end
复制代码
须要注意的是Node并不持有对_prev和_next的强引用, _prev和_next执行的内容是由_dic强引用安全
YYLinkedMap能够理解为是一个双向链表, 方便操做头结点和尾结点.数据结构
@interface _YYLinkedMap : NSObject {
@package
CFMutableDictionaryRef _dic; // do not set object directly key: key, value: Node结点
NSUInteger _totalCost; // 总开销
NSUInteger _totalCount; // 缓存数量
_YYLinkedMapNode *_head; // MRU, do not change it directly 头结点
_YYLinkedMapNode *_tail; // LRU, do not change it directly 尾结点
BOOL _releaseOnMainThread; // 是否在主线程release结点
BOOL _releaseAsynchronously; // 是否异步release结点
}
复制代码
思考: 问什么YYLinkedMap中要使用一个字典呢?多线程
使用字典是为了使双向链表的查询某个结点的操做时间复杂度为O(1), 若是是双向链表获取某一个结点的话须要遍历双向链表, 时间复杂度为O(n)并发
YYLinkedMap中使用CFMutableDictionaryRef建立字典, 而不是使用NSDictionary建立字典的缘由:app
由于NSDictionary的key须要遵循NSCoding协议, 而CFMutableDictionaryRef则不须要, 另外CFMutableDictionaryRef更加靠近底层, 效率更高, 可是建立的_dic的内存须要咱们本身手动回收.异步
YYLinkedMap的接口:
// 在链表头部插入结点
- (void)insertNodeAtHead:(_YYLinkedMapNode *)node;
// 将结点移动到链表头部
- (void)bringNodeToHead:(_YYLinkedMapNode *)node;
// 移除结点
- (void)removeNode:(_YYLinkedMapNode *)node;
// 移除尾结点
- (_YYLinkedMapNode *)removeTailNode;
// 清空全部结点
- (void)removeAll;
复制代码
YYLinkedMap的操做主要是对双向链表进行的插入 删除操做, 下图中虚线为弱引用, 实线为强引用
- (void)insertNodeAtHead:(_YYLinkedMapNode *)node {
// 将结点node保存在_dic中
CFDictionarySetValue(_dic, (__bridge const void *)(node->_key), (__bridge const void *)(node));
// 根据结点的cost开销修改map记录的总开销数
_totalCost += node->_cost;
// 记录缓存个数
_totalCount++;
if (_head) {
// 将结点node设置为头结点
node->_next = _head;
_head->_prev = node;
_head = node;
} else {
// 若是头结点为nil, 说明链表为空, 添加的结点node就是头结点 而且仍是尾结点
_head = _tail = node;
}
}
复制代码
移动结点:
- (void)bringNodeToHead:(_YYLinkedMapNode *)node {
// 若是只有一个结点, 说明不须要移动
if (_head == node) return;
if (_tail == node) {
// 若是node是尾结点, 从新设置尾结点
_tail = node->_prev;
_tail->_next = nil;
} else {
// node既不是头结点又不是尾结点, 至关于删除结点node
node->_next->_prev = node->_prev;
node->_prev->_next = node->_next;
}
// 将node设置为头结点
node->_next = _head;
node->_prev = nil;
_head->_prev = node;
_head = node;
}
复制代码
删除结点:
- (void)removeNode:(_YYLinkedMapNode *)node {
// 将结点node从字典_dic中移除, 注意这是node的引用计数减1
CFDictionaryRemoveValue(_dic, (__bridge const void *)(node->_key));
// 修改总开销
_totalCost -= node->_cost;
// 缓存数量减1
_totalCount--;
// 修改node结点的下一个结点prev指向
if (node->_next) node->_next->_prev = node->_prev;
// 修改node结点的上一个结点的next指向
if (node->_prev) node->_prev->_next = node->_next;
// 若是node是头结点, 将头结点设置为node的下一个结点
if (_head == node) _head = node->_next;
// 若是node是尾结点, 将尾结点设置为node的上一个结点
if (_tail == node) _tail = node->_prev;
}
复制代码
注意这里的结点操做执行的顺序, 考虑node是头结点或者尾结点 以及为中间结点的处理状况. 移除尾结点:
- (_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;
}
复制代码
清空数据:
- (void)removeAll {
// 清空信息
_totalCost = 0;
_totalCount = 0;
_head = nil;
_tail = nil;
if (CFDictionaryGetCount(_dic) > 0) {
// 至关于对_dic进行了一次mutableCopy, 因为_dic是不可变, 因此holder和_dic执行了同一块内存空间(堆空间)
CFMutableDictionaryRef holder = _dic;
// 从新在堆空间申请内存, _dic执行新分配的内存(以前堆空间的内存地址保存在holder中)
_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);
}
}
}
复制代码
清空数据这里做者的操做很巧妙, 利用holder引用dic, 从新给dic申请堆空间, 在指定的队列中实现对holder的release操做.
YYMemoryCache是对外提供的类, 提供了一些配置属性和外界访问的方法.
@interface YYMemoryCache : NSObject
// 缓存名称
@property (nullable, copy) NSString *name;
// 缓存的总数量
@property (readonly) NSUInteger totalCount;
// 缓存的总开销
@property (readonly) NSUInteger totalCost;
// 缓存的数量限制, 默认值是NSUIntegerMax表示不作限制
@property NSUInteger countLimit;
// 缓存的开销限制, 默认值是NSUIntegerMax表示不作限制, 例如设置缓存总开销为200k, 那么当总开销大于200k时就会在后台自动清理
@property NSUInteger costLimit;
// 缓存的时间限制, 默认是DBL_MAX表示不限制
@property NSTimeInterval ageLimit;
// 自动清理时间设置, 默认值是5s, 也就是5秒钟会自动检查清理缓存
@property NSTimeInterval autoTrimInterval;
// 当收到内存警告时, 是否清除全部缓存, 默认是YES
@property BOOL shouldRemoveAllObjectsOnMemoryWarning;
// 当App进入后台时, 是否清除全部缓存, 默认是YES
@property BOOL shouldRemoveAllObjectsWhenEnteringBackground;
// 收到内存警告时执行的block
@property (nullable, copy) void(^didReceiveMemoryWarningBlock)(YYMemoryCache *cache);
// 进入后台时执行的block
@property (nullable, copy) void(^didEnterBackgroundBlock)(YYMemoryCache *cache);
// 当须要移除某个value时, release操做是否在主线程执行, 默认为NO
@property BOOL releaseOnMainThread;
// 当须要移除某个value时, release造做是否异步执行, 默认为YES
@property BOOL releaseAsynchronously;
// 缓存中是否存在key
- (BOOL)containsObjectForKey:(id)key;
// 从缓存中获取和key相关联的value
- (nullable id)objectForKey:(id)key;
// 缓存key-value, 若是value=nil, 表示移除key的缓存
- (void)setObject:(nullable id)object forKey:(id)key;
// 缓存key-value, 指定开销
- (void)setObject:(nullable id)object forKey:(id)key withCost:(NSUInteger)cost;
// 从缓存中移除key
- (void)removeObjectForKey:(id)key;
// 清空缓存
- (void)removeAllObjects;
// 将缓存修剪到指定的数量, 从链表的尾结点移除
- (void)trimToCount:(NSUInteger)count
// 将缓存修剪到指定的开销, 从链表的尾结点移除
- (void)trimToCost:(NSUInteger)cost;
// 将缓存修剪到指定的时间, 大于这个时间的数据都会被清除
- (void)trimToAge:(NSTimeInterval)age;
@end
复制代码
在YYMemoryCahce的初始化方法中, 对一些属性进行了默认参数配置
@implementation YYMemoryCache {
pthread_mutex_t _lock; // 互斥锁
_YYLinkedMap *_lru; // least recent used
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;
// 注册App的通知
[[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;
}
复制代码
在init方法中调用了_trimRecursively方法.
- (void)_trimRecursively {
// 使用weakself, block不会对self进行强引用
__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修饰, 防止self被提早销毁
__strong typeof(_self) self = _self;
if (!self) return;
[self _trimInBackground];
[self _trimRecursively];
});
}
- (void)_trimInBackground {
// 为何是串行队列? 串行队列中的任务会等待上个任务执行完成之后才执行, 是想按照cost->count->age的顺序去清除缓存
dispatch_async(_queue, ^{
[self _trimToCost:self->_costLimit];
[self _trimToCount:self->_countLimit];
[self _trimToAge:self->_ageLimit];
});
}
复制代码
_trimRecursively
方法中使用一个dispatch_after
进行延时操做, 延时时间就是属性autoTrimInterval
, 经过延时递归调用_trimRecursively
方法实现自动清理缓存的功能.
在_trimRecursively
中为何使用__weak
和__strong
去修饰self
呢?
当不使用__weak
和__strong
修饰self
的时候, demo以下:
@interface MyObject : NSObject
@end
@implementation MyObject
- (void)_trimRecursively {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
NSLog(@"1-----");
if (!self) return; // block会捕获self, copy, self引用计数加一.
[self _trimRecursively]; // 由于这里是异步调用, 不会阻塞当前线程(递归调用方法中又会重复对self进行 copy和release操做)
NSLog(@"2-----"); // 执行完block之后, block释放对self的引用, release, self引用计数减一.
});
}
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
MyObject *obj = [[MyObject alloc] init];
[obj _trimRecursively];
}
@end
复制代码
当viewDidLoad
执行完成之后, obj
就应该被释放, 可是在_trimRecursively
的block中一直存在对obj
的强引用, 因此就形成了obj永远不会被释放.
_trimToCost
_trimToCount
_trimToAge
三种清理缓存的方法处理是同样的, 只是清除缓存的依据不同, 因此这里只分析了一种状况.
// static修饰能够在多个文件中定义
// inline内联函数, 提升效率, 在调用的时候是在调用出直接替换, 而普通的函数须要开辟栈空间(push pop操做)
static inline dispatch_queue_t YYMemoryCacheGetReleaseQueue() {
return dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);
}
复制代码
- (void)_trimToCost:(NSUInteger)costLimit {
BOOL finish = NO;
// 避免多线程访问出现数据错乱, 因此选择加锁
pthread_mutex_lock(&_lock);
if (costLimit == 0) {
// 若是内存开销设置为0, 表示清空缓存
[_lru removeAll];
finish = YES;
} else if (_lru->_totalCost <= costLimit) {
// 若是当前缓存的开销小于指定限制开销, 不须要清理
finish = YES;
}
pthread_mutex_unlock(&_lock);
if (finish) return;
// 建立一个数组, 数组中存放的是结点, 为了在指定的线程 异步或者同步release
NSMutableArray *holder = [NSMutableArray new];
// 这里的时间复杂度是O(n)???
while (!finish) {
// 尝试加锁, 若是成功就为0
if (pthread_mutex_trylock(&_lock) == 0) {
// 将须要移除的结点添加到数组中
if (_lru->_totalCost > costLimit) {
// 这里node虽然从dic字典和双向链表中移除了, 可是又加入了数组中, 因此这里node并不会被销毁
_YYLinkedMapNode *node = [_lru removeTailNode];
if (node) [holder addObject:node];
} else {
finish = YES;
}
pthread_mutex_unlock(&_lock);
} else {
// 为了防止争夺资源, sleep10ms
usleep(10 * 1000); //10 ms
}
}
// holder中保存了须要release的node结点
if (holder.count) {
// 根据配置选择是否在主线程中release结点资源
// YYMemoryCacheGetReleaseQueue()是内联函数, 建立的全局并发队列
dispatch_queue_t queue = _lru->_releaseOnMainThread ? dispatch_get_main_queue() : YYMemoryCacheGetReleaseQueue();
// 在队列中移除
dispatch_async(queue, ^{
// 这里个人理解是 block会对holder强引用, 当block执行完成之后holder的引用计数减1
[holder count]; // release in queue
});
}
// 由于block中对holder有强引用, 因此出了}之后, holder的引用计数减1, 不会被销毁, 而是在block中销毁(实如今指定的队列中销毁).
}
复制代码
YYMemoryCache中提供了一些配置属性, 经过重写setter方法, 实现对YYLinkedMap的配置
- (NSUInteger)totalCount {
pthread_mutex_lock(&_lock);
NSUInteger count = _lru->_totalCount;
pthread_mutex_unlock(&_lock);
return count;
}
- (NSUInteger)totalCost {
pthread_mutex_lock(&_lock);
NSUInteger totalCost = _lru->_totalCost;
pthread_mutex_unlock(&_lock);
return totalCost;
}
- (BOOL)releaseOnMainThread {
pthread_mutex_lock(&_lock);
BOOL releaseOnMainThread = _lru->_releaseOnMainThread;
pthread_mutex_unlock(&_lock);
return releaseOnMainThread;
}
- (void)setReleaseOnMainThread:(BOOL)releaseOnMainThread {
pthread_mutex_lock(&_lock);
_lru->_releaseOnMainThread = releaseOnMainThread;
pthread_mutex_unlock(&_lock);
}
- (BOOL)releaseAsynchronously {
pthread_mutex_lock(&_lock);
BOOL releaseAsynchronously = _lru->_releaseAsynchronously;
pthread_mutex_unlock(&_lock);
return releaseAsynchronously;
}
复制代码
查找:
- (BOOL)containsObjectForKey:(id)key {
if (!key) return NO;
// 由于须要操做map中的数据, 加锁
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);
// 先从字典中获取node
_YYLinkedMapNode *node = CFDictionaryGetValue(_lru->_dic, (__bridge const void *)(key));
if (node) {
// 由于操做了node, 因此须要将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];
}
- (void)setObject:(id)object forKey:(id)key withCost:(NSUInteger)cost {
if (!key) return;
// 若是object为nil, 说明是移除key
if (!object) {
[self removeObjectForKey:key];
return;
}
pthread_mutex_lock(&_lock);
// 将node存储在字典中
_YYLinkedMapNode *node = CFDictionaryGetValue(_lru->_dic, (__bridge const void *)(key));
// 获取当前时间, 用于设置node的操做时间
NSTimeInterval now = CACurrentMediaTime();
if (node) {
// 若是node已经存在缓存中, 须要将node的time修改, 而且将node添加到表头
_lru->_totalCost -= node->_cost;
_lru->_totalCost += cost;
node->_cost = cost;
node->_time = now;
node->_value = object;
[_lru bringNodeToHead:node];
} else {
// 若是node不存在, 须要建立node, 而且添加到表头
node = [_YYLinkedMapNode new];
node->_cost = cost;
node->_time = now;
node->_key = key;
node->_value = object;
[_lru insertNodeAtHead:node];
}
// 添加完成之后, 由于_totalCost增长, 须要检查总开销是否超过了设定的标准_costLimit
if (_lru->_totalCost > _costLimit) {
// 在串行队列中异步清除缓存
dispatch_async(_queue, ^{
[self trimToCost:_costLimit];
});
}
// 添加完成之后, 由于_totalCount增长, 须要检查_totalCount是否大于_countLimit
if (_lru->_totalCount > _countLimit) {
// 由于每次只增长一个node, 因此只须要移除最后一个尾结点便可
_YYLinkedMapNode *node = [_lru removeTailNode];
if (_lru->_releaseAsynchronously) {
dispatch_queue_t queue = _lru->_releaseOnMainThread ? dispatch_get_main_queue() : YYMemoryCacheGetReleaseQueue();
dispatch_async(queue, ^{
[node class]; //hold and release in queue
});
} else if (_lru->_releaseOnMainThread && !pthread_main_np()) {
dispatch_async(dispatch_get_main_queue(), ^{
[node class]; //hold and release in queue
});
}
}
pthread_mutex_unlock(&_lock);
}
复制代码
删除:
- (void)removeObjectForKey:(id)key {
if (!key) return;
pthread_mutex_lock(&_lock);
// 获取须要移除的node
_YYLinkedMapNode *node = CFDictionaryGetValue(_lru->_dic, (__bridge const void *)(key));
if (node) {
[_lru removeNode:node];
if (_lru->_releaseAsynchronously) {
dispatch_queue_t queue = _lru->_releaseOnMainThread ? dispatch_get_main_queue() : YYMemoryCacheGetReleaseQueue();
dispatch_async(queue, ^{
[node class]; //hold and release in queue
});
} else if (_lru->_releaseOnMainThread && !pthread_main_np()) {
dispatch_async(dispatch_get_main_queue(), ^{
[node class]; //hold and release in queue
});
}
}
pthread_mutex_unlock(&_lock);
}
- (void)removeAllObjects {
pthread_mutex_lock(&_lock);
[_lru removeAll];
pthread_mutex_unlock(&_lock);
}
复制代码
- (NSString *)description {
if (_name) return [NSString stringWithFormat:@"<%@: %p> (%@)", self.class, self, _name];
else return [NSString stringWithFormat:@"<%@: %p>", self.class, self];
}
复制代码
重写description
方法, 当log YYMemoryCache
的对象时就会调用这个方法, log相关信息.
LRU(least-recently-used): 最近最少使用, 是一种淘汰算法, 若是最近模块数据被访问, 那么未来这块数据被访问的几率也很大. 最久没有被访问的数据, 未来被访问的几率会很小. 将最久没有使用的淘汰掉, 须要使用一个time字段, 记录每次访问的时间, 当须要淘汰的时候, 就选择time最大的淘汰.
MRU(most-recently-used): 最近最常使用, 和LRU相反.
YYMemoryCache须要操做资源时加锁. Pthread_mutex锁.
YYMemoryCache中对清除缓存的操做 访问_YYLinkedMap中的数据 以及getter setter方法都加锁处理, 避免多线程访问的时候出现数据错乱.
// dispatch_semaphore是信号量, 当信号量为1时也能够当作锁来使用, 当信号量小于1时就会等待
dispatch_semaphore_t lock = dispatch_semaphore_create(1);
dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER);
...
dispatch_semaphore_signal(lock);
pthread_mutex_t lock;
pthread_mutex_init(&lock, NULL);
pthread_mutex_lock(&lock);
...
pthread_mutex_unlock(&lock);
复制代码
当没有等待的状况下, dispatch_semaphore的性能要比pthread_mutext高, 可是一旦出现等待的状况, 性能就会降低不少.