本人有若干成套学习视频, 可试看! 可试看! 可试看, 重要的事情说三遍 包含Java, 数据结构与算法, iOS, 安卓, python, flutter等等, 如有需要, 联系微信tsaievan.
YYCache将缓存分成了两部分:
- 内存缓存
- 磁盘缓存
YYMemoryCache是YYCache的内存缓存.
先来看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: 收到内存缓存是否移除所有缓存对象, 默认YESshouldRemoveAllObjectsWhenEnteringBackground: 程序进入后台是否移除所有缓存对象, 默认YESdidReceiveMemoryWarningBlock: app收到内存警告的时候调用的block, 默认为nildidEnterBackgroundBlock: 程序进入后台之后调用的block, 默认为nilreleaseOnMainThread: 键值对是否在主线程释放,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地址










