本人有若干成套学习视频, 可试看! 可试看! 可试看, 重要的事情说三遍 包含Java
, 数据结构与算法
, iOS
, 安卓
, python
, flutter
等等, 如有需要, 联系微信tsaievan
.
YYCache
将缓存分成了两部分:
- 内存缓存
- 磁盘缓存
YYMemoryCache
是YYCache
的内存缓存.
YYMemoryCache是一个快捷的内存缓存, 缓存键值对
与NSDictionary不同, keys是被持有的而不是被拷贝的
API和性能与NSCache一致, 所有方法都是线程安全的
YYMemoryCache与NSCache的不同点
- 使用了LRU(最近最少被使用)算法去移除缓存对象
- 可以设置开销去控制缓存容量,NSCache的缓存限制是不精确的
- 当收到内存警告或者是app进入后台时, 可以自动清除缓存对象
先来看YYMemoryCache
是如何使用的:
@interface YYMemoryCache : NSObject
#pragma mark - 属性
///< 缓存的名称
@property (nullable, copy) NSString *name;
///< 缓存对象的总数, 只读
@property (readonly) NSUInteger totalCount;
///< 缓存对象的总开销, 只读
@property (readonly) NSUInteger totalCost;
#pragma mark - 限制
///< 缓存的最大容量
@property NSUInteger countLimit;
///< 在开始移除缓存对象前缓存的最大开销
@property NSUInteger costLimit;
///< 缓存的最大失效时间
@property NSTimeInterval ageLimit;
///< 自检时间, 默认5秒, 递归调用, 自检缓存是否超过了限制, 如果超过了限制, 会移除缓存
@property NSTimeInterval autoTrimInterval;
///< 收到内存缓存是否移除所有缓存对象, 默认YES
@property BOOL shouldRemoveAllObjectsOnMemoryWarning;
///< 程序进入后台是否移除所有缓存对象, 默认YES
@property BOOL shouldRemoveAllObjectsWhenEnteringBackground;
///< app收到内存警告的时候调用的block, 默认为nil
@property (nullable, copy) void(^didReceiveMemoryWarningBlock)(YYMemoryCache *cache);
///< 程序进入后台之后调用的block, 默认为nil
@property (nullable, copy) void(^didEnterBackgroundBlock)(YYMemoryCache *cache);
///< 键值对是否在主线程释放, YES: 主线程, NO: 子线程
///< 默认为NO
///< 必须设置为YES, 如果键值对中包含必须在主线程释放的对象(比如 UIView/ CALayer)
@property BOOL releaseOnMainThread;
///< 是否异步释放以避免阻塞访问内存缓存的方法, 否则要在访问方法中释放 (例如removeObjectForKey:)
///< 默认是YES(即异步释放)
@property BOOL releaseAsynchronously;
#pragma mark - 访问方法
///< 缓存中是否包含某一个key
- (BOOL)containsObjectForKey:(id)key;
///< 从缓存中取key对应的对象
- (nullable id)objectForKey:(id)key;
///< 向缓存中存对象
///< 如果key为nil. 直接return
///< 如果object为nil. 则清除key对应的对象
- (void)setObject:(nullable id)object forKey:(id)key;
///< 向缓存中存对象
///< cost是开销, 和键值对关联的
- (void)setObject:(nullable id)object forKey:(id)key withCost:(NSUInteger)cost;
///< 清除缓存中某个key对应的对象
- (void)removeObjectForKey:(id)key;
///< 清除缓存中所有对象
- (void)removeAllObjects;
#pragma mark - 部分清除缓存
///< 利用LRU(最近最少使用 least recently used)算法, 从尾节点清除缓存, 直到总数小于等于设定的限制值
- (void)trimToCount:(NSUInteger)count;
///< 从尾节点清除缓存, 直到总开销小于等于设定的限制值
- (void)trimToCost:(NSUInteger)cost;
///< 从尾节点清除缓存, 将所有过期的缓存对象全部删除
///< age表示对象缓存的最长时间(单位是秒)
- (void)trimToAge:(NSTimeInterval)age;
@end
通过YYMemoryCache
暴露的接口得知:
接口总共分为四大块:
- 属性
- 限制
- 访问方法
- 清除部分缓存
<一>属性中:
你可以为缓存设置name
, 便于管理不同的缓存
totalCount
和totalCost
是只读的, 你可以通过这个属性, 获取到当前缓存的缓存对象的总数
和总开销
<二>限制中:
有三大维度限制缓存的容量:
-
countLimit
: 缓存的最大容量 -
costLimit
: 在开始移除缓存对象前缓存的最大开销 -
ageLimit
: 缓存的最大失效时间
以及一些其他的属性:
autoTrimInterval
: 自检时间, 默认5秒, 递归调用, 自检缓存是否超过了限制, 如果超过了限制, 会移除缓存.shouldRemoveAllObjectsOnMemoryWarning
: 收到内存缓存是否移除所有缓存对象, 默认YES
shouldRemoveAllObjectsWhenEnteringBackground
: 程序进入后台是否移除所有缓存对象, 默认YES
didReceiveMemoryWarningBlock
: app收到内存警告的时候调用的block, 默认为nil
didEnterBackgroundBlock
: 程序进入后台之后调用的block, 默认为nil
releaseOnMainThread
: 键值对是否在主线程释放,YES
: 主线程,NO
: 子线程 . 默认为NO
, 如果键值对中包含必须在主线程释放的对象(比如UIView
/CALayer
), 此时必须设置为YES
.releaseAsynchronously
: 是否异步释放以避免阻塞访问内存缓存的方法, 否则要在访问方法中释放 (例如removeObjectForKey:
), 默认是YES
(即异步释放)
<三>访问方法:
访问方法其实也就是增
,删
, 改
, 查
的过程, 这几个访问方法跟传统的例如NSCache
的方法其实是差不多的.
- 增:
- 改:
///< 向缓存中存对象
///< 如果key为nil. 直接return
///< 如果object为nil. 则清除key对应的对象
- (void)setObject:(nullable id)object forKey:(id)key;
///< 向缓存中存对象
///< cost是开销, 和键值对关联的
- (void)setObject:(nullable id)object forKey:(id)key withCost:(NSUInteger)cost;
- 删:
///< 清除缓存中某个key对应的对象
- (void)removeObjectForKey:(id)key;
///< 清除缓存中所有对象
- (void)removeAllObjects;
- 查:
///< 缓存中是否包含某一个key
- (BOOL)containsObjectForKey:(id)key;
///< 从缓存中取key对应的对象
- (nullable id)objectForKey:(id)key;
<四>部分清除缓存:
这个主要还是围绕三大维度来进行操作的:
-
count
: 缓存对象的数量
///< 利用LRU(最近最少使用 least recently used)算法, 从尾节点清除缓存, 直到总数小于等于设定的限制值
- (void)trimToCount:(NSUInteger)count;
-
cost
: 缓存对象的开销
///< 从尾节点清除缓存, 直到总开销小于等于设定的限制值
- (void)trimToCost:(NSUInteger)cost;
-
age
: 缓存要保存的时间
///< 从尾节点清除缓存, 将所有过期的缓存对象全部删除
///< age表示对象缓存的最长时间(单位是秒)
- (void)trimToAge:(NSTimeInterval)age;
再来看看YYMemoryCache
内部的实现原理:
YYMemoryCache
内部是用字典
+ 链表
来实现的:
字典
用来存储数据, 数据是被包装在node(节点)
对象中的
链表
用来实现LRU
算法, 实现从三大维度层面上管理内存缓存
YYMemoryCache
中的链表是一个双向链表_YYLinkedMap
:
@interface _YYLinkedMap : NSObject {
@package
CFMutableDictionaryRef _dic; // do not set object directly
NSUInteger _totalCost;
NSUInteger _totalCount;
///< 头部节点
_YYLinkedMapNode *_head; // MRU, 最近经常使用
///< 尾部节点
_YYLinkedMapNode *_tail; // LRU, 最近不常使用
BOOL _releaseOnMainThread;
BOOL _releaseAsynchronously;
}
从_YYLinkedMap
的成员变量来看:
_YYLinkedMap
主要由三个部分组成:
- 一个
字典
- 头部节点
- 尾部节点
链表中的每一个节点都封装成了一个对象_YYLinkedMapNode
:
@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;
}
节点中包含了指向上一个节点的指针_prev
以及指向下一个节点的指针_next
双向链表的数据结构如下图所示:
_YYLinkedMapNode
中还有两个成员变量
key
-
value
这样就把需要保存的键值对(key
-value
)封装成了一个对象.
而链表_YYLinkedMap
中的字典保存的实际上就是一个个的节点_YYLinkedMapNode
, 而每一个节点_YYLinkedMapNode
都能根据_prev
和_next
指针找到上一个和下一个节点
基本的数据结构介绍完了, 那我们就来看看YYMemoryCache
是怎么跑起来的:
这是YYMemoryCache
的初始化方法:
- (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;
}
初始化方法里面做了这几件事:
- 初始化一把锁
- 创建一个链表对象
- 创建一个串行队列
- 对一些成员变量进行初始化赋值
- 注册通知
- 递归调用清除缓存的方法
我们就从递归调用清除缓存的方法中看看内部是怎么实现的:
///< 递归清除缓存
- (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];
});
}
这个方法先是开了一个GCD
定时器, 然后定时在子线程中清除缓存_trimInBackground
, 然后再递归调用这个方法_trimRecursively
_trimInBackground
方法中就是从三个维度去清除缓存
_trimToCost
_trimToCount
_trimToAge
我们就以_trimToCount
来说明他是怎么从缓存数量
这个维度去清除缓存的:
///< 清除缓存到缓存数量的上限
- (void)_trimToCount:(NSUInteger)countLimit {
BOOL finish = NO;
pthread_mutex_lock(&_lock); ///< 加锁
if (countLimit == 0) { ///< 如果缓存数量限制为0, 就清除全部节点
[_lru removeAll];
finish = YES;
} else if (_lru->_totalCount <= countLimit) { ///< 总缓存数量小于等于缓存数量限制
finish = YES;
}
pthread_mutex_unlock(&_lock); ///< 解锁
if (finish) return; ///< 如果finish为YES, 直接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 { ///< 如果不能锁成功, 就线程阻塞10毫秒,然后再尝试加锁, 执行remove操作
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
});
}
}
- 首先, 初始化一个
BOOL
类型的指示变量finish
, 初始值为NO
- 在对链表进行读写时, 要加上线程锁
- 如果我们设置的数量上限
countLimit
为0
的话, 那么就是清除链表中的全部节点,finish
置为YES
- 如果当前链表中节点的总数量小于等于
countLimit
, 那就什么都不做,finish
置为YES
- 如果链表中的节点总数大于
countLimit
, 那就要逐一去掉尾部节点, 直到链表中节点的总数量小于等于countLimit
.
那么, 实现这个需求, 最好的方法就是开启一个while
循环:
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 { ///< 如果不能锁成功, 就线程阻塞10毫秒,然后再尝试加锁, 执行remove操作
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
});
}
在这个清除缓存的方法中, 最重要的其实就是两个方法:
-
removeAll
清除所有节点
///< 移除所有
- (void)removeAll {
///< 总开销为0
_totalCost = 0;
///< 总节点数为0
_totalCount = 0;
///< 头部节点和尾部节点都置空
_head = nil;
_tail = nil;
if (CFDictionaryGetCount(_dic) > 0) {
///< 创建一个新的变量去接这个字典的地址
CFMutableDictionaryRef holder = _dic;
///< 重新建一个可变字典赋值给_dic
_dic = CFDictionaryCreateMutable(CFAllocatorGetDefault(), 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
///< 异步释放
///< 把原来的_dic地址指向的内存空间释放掉了
///< _dic指向了新的地址
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);
}
}
}
思路就是:
- 将一些成员变量全部置空, 比如
头部节点
,尾部节点
,总开销
,总数量
等 - 用一个变量接收老的
_dic
变量 - 新创建一个字典赋值给
_dic
- 将字典中保存的键值对异步释放
-
removeTailNode
清除尾部节点
///< 移除尾部节点, 有返回值
- (_YYLinkedMapNode *)removeTailNode {
///< 如果不存在尾部节点, 直接返回nil
if (!_tail) return nil;
_YYLinkedMapNode *tail = _tail;
///< 通过尾部节点的key值, 从字典中移除尾部节点
CFDictionaryRemoveValue(_dic, (__bridge const void *)(_tail->_key));
///< 总开销中减去尾部节点的开销
_totalCost -= _tail->_cost;
///< 缓存的总节点数减去1
_totalCount--;
if (_head == _tail) { ///< 如果头部节点等于尾部节点, 那么说明链表中只有一个节点
_head = _tail = nil; ///< 那么将头部节点和尾部节点都置空即可
} else { ///< 表明链表中不止一个节点
_tail = _tail->_prev; ///< 将尾部节点的上一个节点赋值给尾部节点
_tail->_next = nil; ///< 尾部节点的下一个节点赋值为nil
}
///< 此时返回的是移除的尾部节点的地址, 而现在的_tail的地址已经指向了当前的尾部节点的地址
return tail;
}
思路就是:
- 先保存尾部节点的旧值, 这个是最后要返回出去的
- 通过尾部节点的key值, 从字典中移除尾部节点
- 总开销中减去尾部节点的开销
- 缓存的总节点数减去1
- 如果头部节点等于尾部节点, 那么说明链表中只有一个节点,那么将头部节点和尾部节点都置空即可
- 否则, 表明链表中不止一个节点, 将尾部节点的上一个节点赋值给尾部节点, 尾部节点的下一个节点赋值为nil
而YYMemoryCache
中的访问方法中的清除全部缓存removeAllObjects
实际上就是线程安全地调用链表中的清除全部节点
的方法
那我们再看看YYMemoryCache
中的利用key
值存
和取
是怎么实现的:
<一> 存:
///< 将对象存缓存
- (void)setObject:(id)object forKey:(id)key withCost:(NSUInteger)cost {
if (!key) return; ///< 如果key为空, 直接return
if (!object) {
[self removeObjectForKey:key];
return;
}
pthread_mutex_lock(&_lock);
///< 先根据key值, 取出node
_YYLinkedMapNode *node = CFDictionaryGetValue(_lru->_dic, (__bridge const void *)(key));
NSTimeInterval now = CACurrentMediaTime();
///< 存数据的时候, 把最近使用的放在最前面的!
if (node) { ///< 如果node不为空
_lru->_totalCost -= node->_cost; ///< 总开销先减去node的开销
_lru->_totalCost += cost; ///< 再加上当前传入的参数的开销
node->_cost = cost;
node->_time = now;
node->_value = object;
[_lru bringNodeToHead:node]; ///< 将节点移动到头部
} else { ///< 如果node为空
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]; ///< 那么就要移除尾节点
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);
}
- 如果
key
为空, 直接return
- 如果
object
为空, 则调用removeObjectForKey
, 将key
对应的值清掉 - 如果
key
和object
都不为空, 这个时候就开始存
了 - 首先, 根据
key
值取出节点node
- 获取当前的时间
- 进行判断
- 如果
node
存在, 将链表的总开销减去当前node
的开销, 然后再将链表的总开销加上传入的参数cost
,node
的cost
赋值新值,node
的time
赋值当前时间,node
的value
赋值object
, 最后将node
移动到头部 - 如果
node
为空, 则新建一个node
. 再将node
的cost
,time
,key
,value
依次赋值, 然后将此node
插入到头部
- 最后就是盘点链表的总开销和缓存对象的总数量是否大于限定值, 如果大于, 就要进行清除.
<二>取:
///< 从字典中取出节点, 并将节点的时间变量更新, 并将节点移动到链表头部
- (id)objectForKey:(id)key {
if (!key) return nil;
pthread_mutex_lock(&_lock);
///< 从字典中取出节点
_YYLinkedMapNode *node = CFDictionaryGetValue(_lru->_dic, (__bridge const void *)(key));
if (node) {
///< 节点s的时间属性的更新
node->_time = CACurrentMediaTime();
///< 将节点移动到链表头部
[_lru bringNodeToHead:node];
}
pthread_mutex_unlock(&_lock);
return node ? node->_value : nil; ///< 如果节点不存在, 返回nil
}
取相对于存就比较简单了:
- 根据
key
从链表中取出节点node
- 如果
node
存在, 就将node
的time
更新为当前时间 - 并将此节点移动到链表头部
- 最后, 利用三元表达式, 判断
node
是否为空, 如果不为空, 则将node
的value
返回出去, 如果为空, 则返回nil
关于链表的操作, 有以下几个方法:
-
insertNodeAtHead:
在头部插入一个节点, 并更新总数 -
bringNodeToHead:
将内部节点移动到头部 -
removeNode:
移除一个内部节点, 并更新总数 -
removeTailNode
如果存在的话, 移除尾节点 -
removeAll
在子线程中移除所有节点
具体的实现我就不做具体介绍了, 下面是代码, 以及我翻译的注释,大家一看就能明白
///< 将节点插入到头部
- (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) { ///< 如果需要移动的节点是尾部节点
///< node节点的上一个, 现在变成尾节点
_tail = node->_prev;
_tail->_next = nil;
} else { ///< 如果需要移动的节点不是尾部节点
node->_next->_prev = node->_prev; ///< node节点的下一个节点的上一个节点本来是node节点的, 现在指向node节点的上一个节点
node->_prev->_next = node->_next; ///< node节点的上一个节点的下一个节点本来也是node节点的, 现在指向node节点的下一个节点
}
node->_next = _head; ///< node现在的下一个节点变成原来的头节点
node->_prev = nil; ///< node的上一个节点指向nil
_head->_prev = node; ///< 原来的头节点的上一个节点, 之前指向nil, 现在指向node节点
_head = node; ///< 头部节点的指针指向node节点, 即现在node节点变成头部节点
}
///< 移除节点
- (void)removeNode:(_YYLinkedMapNode *)node {
///< 根据node的key, 从字典中移除node
CFDictionaryRemoveValue(_dic, (__bridge const void *)(node->_key));
///< 减去这个节点的开销, 每个开销是api传过来的,也就是程序员自己定义的
_totalCost -= node->_cost;
///< 节点总数减1
_totalCount--;
///< 如果要移除的node有下一个节点, 那么就将node节点的上一个节点赋值给下一个节点的上一个节点
if (node->_next) node->_next->_prev = node->_prev;
///< 如果要移除的node有上一个节点, 那么就将node节点的下一个节点赋值给上一个节点的下一个节点
if (node->_prev) node->_prev->_next = node->_next;
///< 如果node节点为头部节点, 那么将node节点的下一个节点赋值给头部节点
if (_head == node) _head = node->_next;
///< 如果node节点为尾部节点, 那么将node节点的上一个节点赋值给尾部节点
if (_tail == node) _tail = node->_prev;
}
///< 移除尾部节点, 有返回值
- (_YYLinkedMapNode *)removeTailNode {
///< 如果不存在尾部节点, 直接返回nil
if (!_tail) return nil;
_YYLinkedMapNode *tail = _tail;
///< 通过尾部节点的key值, 从字典中移除尾部节点
CFDictionaryRemoveValue(_dic, (__bridge const void *)(_tail->_key));
///< 总开销中减去尾部节点的开销
_totalCost -= _tail->_cost;
///< 缓存的总节点数减去1
_totalCount--;
if (_head == _tail) { ///< 如果头部节点等于尾部节点, 那么说明链表中只有一个节点
_head = _tail = nil; ///< 那么将头部节点和尾部节点都置空即可
} else { ///< 表明链表中不止一个节点
_tail = _tail->_prev; ///< 将尾部节点的上一个节点赋值给尾部节点
_tail->_next = nil; ///< 尾部节点的下一个节点赋值为nil
}
///< 此时返回的是移除的尾部节点的地址, 而现在的_tail的地址已经指向了当前的尾部节点的地址
return tail;
}
///< 移除所有
- (void)removeAll {
///< 总开销为0
_totalCost = 0;
///< 总节点数为0
_totalCount = 0;
///< 头部节点和尾部节点都置空
_head = nil;
_tail = nil;
if (CFDictionaryGetCount(_dic) > 0) {
///< 创建一个新的变量去接这个字典的地址
CFMutableDictionaryRef holder = _dic;
///< 重新建一个可变字典赋值给_dic
_dic = CFDictionaryCreateMutable(CFAllocatorGetDefault(), 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
///< 异步释放
///< 把原来的_dic地址指向的内存空间释放掉了
///< _dic指向了新的地址
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);
}
}
}
以上就是我的总结, 感谢大家!
YYCache的github地址