YYCache 源码
2021-08-03 本文已影响0人
麻辣柠檬
前段时间学习了一遍 YYCache 中的 YYMemoryCache
将其中的英文机翻为中文,并添加了自己对代码的理解
YYCache.h
//
// YYCache.h
// YYKit <https://github.com/ibireme/YYKit>
//
// Created by ibireme on 15/2/13.
// Copyright (c) 2015 ibireme.
//
// This source code is licensed under the MIT-style license found in the
// LICENSE file in the root directory of this source tree.
//
#import <Foundation/Foundation.h>
@class YYMemoryCache, YYDiskCache;
NS_ASSUME_NONNULL_BEGIN
/**
`YYCache`是线程安全的键值缓存。
它使用“YYMemoryCache”将对象存储在一个小而快速的内存缓存中,
并使用“YYDiskCache”将对象持久化到大而慢的磁盘缓存中.
有关详细信息,请参见“YYMemoryCache”和“YYDiskCache”.
*/
@interface YYCache : NSObject
/** 缓存的名称,只读. */
@property (copy, readonly) NSString *name;
/** 底层内存缓存。有关详细信息,请参见“YYMemoryCache”.*/
@property (strong, readonly) YYMemoryCache *memoryCache;
/** 底层磁盘缓存。有关详细信息,请参见“YYDiskCache”.*/
@property (strong, readonly) YYDiskCache *diskCache;
/**
使用指定的名称创建新实例.
具有相同名称的多个实例将使缓存不稳定.
@param name 缓存的名称。它将创建一个名字作为应用程序磁盘缓存缓存字典.一旦初始化,就不应该重写此名称.
@result 一个新的缓存对象,如果发生错误则为nil.
*/
- (nullable instancetype)initWithName:(NSString *)name;
/**
使用指定的路径创建新实例.
具有相同名称的多个实例将使缓存不稳定.
@param path 缓存将保存在该完整路径.一旦初始化,就不应该重写此路径.
@result 一个新的缓存对象,如果发生错误则为nil.
*/
- (nullable instancetype)initWithPath:(NSString *)path NS_DESIGNATED_INITIALIZER;
/**
初始化实例对象
使用指定的名称创建新实例.
具有相同名称的多个实例将使缓存不稳定.
@param name 缓存的名称. 它将创建一个名字作为应用程序磁盘缓存缓存字典。 一旦初始化,就不应该重写此名称.
@result 一个新的缓存对象,如果发生错误则为nil.
*/
+ (nullable instancetype)cacheWithName:(NSString *)name;
/**
初始化实例对象
使用指定的路径创建新实例。
具有相同名称的多个实例将使缓存不稳定.
@param path 缓存将保存在该完整路径.一旦初始化,就不应该重写此路径.
@result 一个新的缓存对象,如果发生错误则为nil.
*/
+ (nullable instancetype)cacheWithPath:(NSString *)path;
- (instancetype)init UNAVAILABLE_ATTRIBUTE;
+ (instancetype)new UNAVAILABLE_ATTRIBUTE;
#pragma mark - 访问方法
///=============================================================================
/// @name 访问方法
///=============================================================================
/**
返回一个布尔值,检查给定的Key是否在缓存中.
此方法可以阻止调用线程,直到文件读取完成。
@param key 标识值的字符串。如果为空,则返回“false”。
@return value是否在缓存中。
*/
- (BOOL)containsObjectForKey:(NSString *)key;
/**
返回一个布尔值,检查给定key是否在缓存中,结束后进行block回调
此方法立即返回(不阻塞当前线程),并在操作完成时调用后台队列中传递的block
@param key 标识值的字符串。如果为nil,则返回“false”.
@param block 完成后将在后台队列中调用的block
*/
- (void)containsObjectForKey:(NSString *)key withBlock:(nullable void(^)(NSString *key, BOOL contains))block;
/**
返回与给定key相关联的Value。
此方法可能会阻塞调用线程,直到文件读取完成。
@param key 标识Key的字符串。如果是nil,就返回nil。
@return 与Key关联的Value,如果没有,则为nil。
*/
- (nullable id<NSCoding>)objectForKey:(NSString *)key;
/**
返回与给定Key关联的value。
此方法立即返回(不阻塞当前线程),并当操作完成时调用后台队列中传递的block
when the operation finished.
@param key A string identifying the value. If nil, just return nil.
@param block A block which will be invoked in background queue when finished.
*/
- (void)objectForKey:(NSString *)key withBlock:(nullable void(^)(NSString *key, id<NSCoding> object))block;
/**
设置缓存中指定key的Value。
此方法可能会阻塞调用线程,直到文件写入完成。
@param object 要存储在缓存中的对象。如果没有,则调用 `removeObjectForKey:`.
@param key 与value关联的key。如果为nil,则此方法无效.
*/
- (void)setObject:(nullable id<NSCoding>)object forKey:(NSString *)key;
/**
设置缓存中指定key的value。
此方法立即返回(不阻塞当前线程),并当操作完成时调用后台队列中传递的block
@param object 要存储在缓存中的对象。如果没有,则调用 `removeObjectForKey:`.
@param block 完成后将在后台队列中调用的block。
*/
- (void)setObject:(nullable id<NSCoding>)object forKey:(NSString *)key withBlock:(nullable void(^)(void))block;
/**
删除缓存中指定key的value。
此方法可能会阻塞调用线程,直到文件删除完成。
@param key 标识要删除的value的key。如果为nil,则此方法无效。
*/
- (void)removeObjectForKey:(NSString *)key;
/**
删除缓存中指定key的value。
此方法立即返回(不阻塞当前线程),并当操作完成时调用后台队列中传递的block
@param key 标识要删除的value的key。如果为nil,则此方法无效。
@param block 完成后将在后台队列中调用的block。
*/
- (void)removeObjectForKey:(NSString *)key withBlock:(nullable void(^)(NSString *key))block;
/**
清空缓存。
此方法可能会阻塞调用线程,直到文件删除完成。
*/
- (void)removeAllObjects;
/**
清空缓存。
此方法立即返回(不阻塞当前线程),并当操作完成时调用后台队列中传递的block
@param block 完成后将在后台队列中调用的block。
*/
- (void)removeAllObjectsWithBlock:(void(^)(void))block;
/**
用block清空缓存。
该的方法立即返回(不阻塞当前线程),并在后台执行带块的清除操作。
@warning 不应在这些block中向该实例对象发送消息。
@param progress 删除时将调用此block,传递nil以忽略。
@param end 此block将在结束时调用,传递nil以忽略。
*/
- (void)removeAllObjectsWithProgressBlock:(nullable void(^)(int removedCount, int totalCount))progress
endBlock:(nullable void(^)(BOOL error))end;
@end
NS_ASSUME_NONNULL_END
YYCache.m
//
// YYCache.m
// YYKit <https://github.com/ibireme/YYKit>
//
// Created by ibireme on 15/2/13.
// Copyright (c) 2015 ibireme.
//
// This source code is licensed under the MIT-style license found in the
// LICENSE file in the root directory of this source tree.
//
#import "YYCache.h"
#import "YYMemoryCache.h"
#import "YYDiskCache.h"
@implementation YYCache
- (instancetype) init {
NSLog(@"Use \"initWithName\" or \"initWithPath\" to create YYCache instance.");
return [self initWithPath:@""];
}
#pragma mark 使用指定的名称创建新实例
- (instancetype)initWithName:(NSString *)name {
// 如果名字为空则返回空
if (name.length == 0) return nil;
// 获取 NSCachesDirectory 路径
NSString *cacheFolder = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) firstObject];
// 路径拼接名称 并调用路径初始化
NSString *path = [cacheFolder stringByAppendingPathComponent:name];
return [self initWithPath:path];
}
#pragma mark 使用指定的路径创建新实例.
- (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;
}
#pragma mark 使用指定的名称创建新实例.
+ (instancetype)cacheWithName:(NSString *)name {
return [[self alloc] initWithName:name];
}
#pragma mark 使用指定的路径创建新实例。
+ (instancetype)cacheWithPath:(NSString *)path {
return [[self alloc] initWithPath:path];
}
#pragma mark 返回一个布尔值,检查给定的Key是否在缓存中.
- (BOOL)containsObjectForKey:(NSString *)key {
// 检查内存缓存 或 磁盘缓存中 是否有该key
return [_memoryCache containsObjectForKey:key] || [_diskCache containsObjectForKey:key];
}
#pragma mark 返回一个布尔值,检查给定key是否在缓存中,结束后进行block回调
- (void)containsObjectForKey:(NSString *)key withBlock:(void (^)(NSString *key, BOOL contains))block {
// 该方法通过block 进行返回,如果没有block,则直接return
if (!block) return;
// 通过内存缓存,检查是否存在该key
if ([_memoryCache containsObjectForKey:key]) {
// 内存缓存存在,则通过全局子线程返回
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 通过block 返回该 key
block(key, YES);
});
} else {
// 内存缓存不存在,则检查磁盘缓存,并通过block返回
[_diskCache containsObjectForKey:key withBlock:block];
}
}
#pragma mark 返回与给定key相关联的Value。
- (id<NSCoding>)objectForKey:(NSString *)key {
// 通过内存缓存 返回key所对应的value
id<NSCoding> object = [_memoryCache objectForKey:key];
// 如果value 为 空
if (!object) {
// 则 通过磁盘缓存 返回key所对应的value
object = [_diskCache objectForKey:key];
// 如果磁盘缓存 不为空
if (object) {
// 则通过 key 将该对象 value 存入到磁盘缓存中,方便下次使用
[_memoryCache setObject:object forKey:key];
}
}
// 返回对象,如果没有 value 则为 空
return object;
}
#pragma mark 返回与给定Key关联的value。
- (void)objectForKey:(NSString *)key withBlock:(void (^)(NSString *key, id<NSCoding> object))block {
// 该方法通过block 进行返回,如果没有block,则直接return
if (!block) return;
// 通过内存缓存 返回key所对应的value
id<NSCoding> object = [_memoryCache objectForKey:key];
// 如果 value 不为空
if (object) {
// 则通过全局子线程 返回该 key 与对应的 value
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 通过block 返回该 key 与 value
block(key, object);
});
} else {
// 否则 通过磁盘缓存 返回 key 所对应的value
[_diskCache objectForKey:key withBlock:^(NSString *key, id<NSCoding> object) {
// 如果 value 不为空 并且 内存缓存中的key 为空的话
if (object && ![_memoryCache objectForKey:key]) {
// 则通过 key 将该对象 value 存入到磁盘缓存中,方便下次使用
[_memoryCache setObject:object forKey:key];
}
// 通过block 返回该 key 与 value
block(key, object);
}];
}
}
#pragma mark 设置缓存中指定key的Value。
- (void)setObject:(id<NSCoding>)object forKey:(NSString *)key {
// 在内存缓存中设置该 key 与 value
[_memoryCache setObject:object forKey:key];
// 在磁盘缓存中设置该 key 与 value
[_diskCache setObject:object forKey:key];
}
#pragma mark 设置缓存中指定key的value。
- (void)setObject:(id<NSCoding>)object forKey:(NSString *)key withBlock:(void (^)(void))block {
// 在内存缓存中设置该 key 与 value
[_memoryCache setObject:object forKey:key];
// 在磁盘缓存中设置该 key 与 value 并在完成时通过block返回
// 由于 内存缓存一定比磁盘缓存要快,所以不需要使用 [memoryCache withBlock]
[_diskCache setObject:object forKey:key withBlock:block];
}
#pragma mark 删除缓存中指定key的value。
- (void)removeObjectForKey:(NSString *)key {
// 删除内存缓存中该 key 与 value
[_memoryCache removeObjectForKey:key];
// 删除磁盘缓存中该 key 与 value
[_diskCache removeObjectForKey:key];
}
#pragma mark 删除缓存中指定key的value。
- (void)removeObjectForKey:(NSString *)key withBlock:(void (^)(NSString *key))block {
// 删除内存缓存中该 key 与 value
[_memoryCache removeObjectForKey:key];
// 删除磁盘缓存中该 key 与 value
// 由于 内存缓存一定比磁盘缓存要快,所以不需要使用 [memoryCache withBlock]
[_diskCache removeObjectForKey:key withBlock:block];
}
#pragma mark 清空缓存。
- (void)removeAllObjects {
// 清空内存缓存中所有数据
[_memoryCache removeAllObjects];
// 清空磁盘缓存中所有的数据
[_diskCache removeAllObjects];
}
#pragma mark 清空缓存。
- (void)removeAllObjectsWithBlock:(void(^)(void))block {
// 清空内存缓存中所有数据
[_memoryCache removeAllObjects];
// 清空磁盘缓存中所有的数据
// 由于 内存缓存一定比磁盘缓存要快,所以不需要使用 [memoryCache withBlock]
[_diskCache removeAllObjectsWithBlock:block];
}
#pragma mark 用block清空缓存。
- (void)removeAllObjectsWithProgressBlock:(void(^)(int removedCount, int totalCount))progress
endBlock:(void(^)(BOOL error))end {
// 清空内存缓存中所有数据
[_memoryCache removeAllObjects];
// 清空磁盘缓存中所有的数据 并返回进度block 与结束block
[_diskCache removeAllObjectsWithProgressBlock:progress endBlock:end];
}
#pragma mark 打印基本信息
- (NSString *)description {
if (_name) return [NSString stringWithFormat:@"<%@: %p> (%@)", self.class, self, _name];
else return [NSString stringWithFormat:@"<%@: %p>", self.class, self];
}
@end
YYMemoryCache.h
//
// YYMemoryCache.h
// YYKit <https://github.com/ibireme/YYKit>
//
// Created by ibireme on 15/2/7.
// Copyright (c) 2015 ibireme.
//
// This source code is licensed under the MIT-style license found in the
// LICENSE file in the root directory of this source tree.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
/**
YYMemoryCache是一种存储键值对的快速内存缓存.
与NSDictionary不同的是,key是保留的而不是复制的。
API和性能类似于“NSCache”,所有方法都是线程安全的。
YYMemoryCache对象与NSCache的区别有以下几个方面:
* 它使用LRU(最近最少使用)删除对象;NSCache的逐出方法是不确定的。
* 它可以通过成本、计数和年龄来控制;NSCache的限制是不精确的。
* 它可以配置为在接收内存时自动逐出对象警告或应用程序输入背景。
YYMemoryCache中“Access Methods”的时间复杂度为 (O(1))。
*/
@interface YYMemoryCache : NSObject
#pragma mark - Attribute
///=============================================================================
/// @name Attribute
///=============================================================================
/** 缓存的名称。默认值为零. */
@property (nullable, copy) NSString *name;
/** 缓存中的对象数(只读) */
@property (readonly) NSUInteger totalCount;
/** 缓存中对象的总开销(只读). */
@property (readonly) NSUInteger totalCost;
#pragma mark - Limit
///=============================================================================
/// @name Limit
///=============================================================================
/**
缓存可以存储的的最大对象数。
@discussion 默认值是 NSUIntegerMax,这意味着没有限制。
这不是一个严格的限制,如果缓存超过了限制,那么缓存可以稍后在backgound线程中收回。
*/
@property NSUInteger countLimit;
/**
缓存在开始逐出对象之前可以保留的最大总开销。
@discussion 默认值是 NSUIntegerMax,这意味着没有限制。
这不是一个严格的限制,如果缓存超过了限制,那么缓存可以稍后在backgound线程中收回。
*/
@property NSUInteger costLimit;
/**
缓存中对象的最长过期时间。
@discussion 默认值是 DBL_MAX,这意味着没有限制。
这不是一个严格的限制如果一个对象超过了限制,这个对象可能会稍后在backgound线程中被逐出。
*/
@property NSTimeInterval ageLimit;
/**
自动微调检查时间间隔(秒)。默认值为5.0。
@discussion 缓存中有一个内部计时器,用于检查缓存是否达到它的极限,如果达到极限,它开始逐出对象。
*/
@property NSTimeInterval autoTrimInterval;
/**
If `YES`, the cache will remove all objects when the app receives a memory warning.
The default value is `YES`.
*/
@property BOOL shouldRemoveAllObjectsOnMemoryWarning;
/**
如果“是”,当应用程序收到内存警告时,缓存将删除所有对象。
默认值为“YES”。
*/
@property BOOL shouldRemoveAllObjectsWhenEnteringBackground;
/**
当应用程序收到内存警告时要执行的block。
默认值为nil。
*/
@property (nullable, copy) void(^didReceiveMemoryWarningBlock)(YYMemoryCache *cache);
/**
当应用程序进入后台时要执行的block。
默认值为nil。
*/
@property (nullable, copy) void(^didEnterBackgroundBlock)(YYMemoryCache *cache);
/**
如果“YES”,则在主线程上释放键值对,否则在子线程上释放。默认为否。
@discussion 如果key value对象包含(如UIView/CALayer)则应该在主线程中释放的对象。
*/
@property BOOL releaseOnMainThread;
/**
如果“YES”,则将异步释放键值对以避免阻塞访问方法,否则将在访问方法中释放
(例如removeObjectForKey:)。默认值为“是”。
*/
@property BOOL releaseAsynchronously;
#pragma mark - Access Methods
///=============================================================================
/// @name Access Methods
///=============================================================================
/**
返回一个布尔值,该值指示给定的key是否在缓存中。
@param key value的key。如果为nil,则返回“NO”。
@return key是否在缓存中。
*/
- (BOOL)containsObjectForKey:(id)key;
/**
返回与给定key关联的value。
@param key value的key。如果是nil,就返回nil。
@return 与key关联的value,如果没有value与key关联,则为nil。
*/
- (nullable id)objectForKey:(id)key;
/**
设置缓存中指定key的value(0)。
@param object 要存储在缓存中的对象。如果nil, 则调用 `removeObjectForKey:`.
@param key 与value关联的key。如果为nil,则此方法无效。
@discussion 与NSMutableDictionary对象不同,缓存不会复制key放入其中的对象。
*/
- (void)setObject:(nullable id)object forKey:(id)key;
/**
设置缓存中指定key的value,并关联该key的value
以指定的cost配对。
@param object 要存储在缓存中的对象。如果nil, 则调用 `removeObjectForKey:`.
@param key 与value关联的key。如果为nil,则此方法无效。
@param cost 与键值对关联的cost。
@discussion 与NSMutableDictionary对象不同,缓存不会复制key放入其中的对象。
*/
- (void)setObject:(nullable id)object forKey:(id)key withCost:(NSUInteger)cost;
/**
删除缓存中指定key的value。
@param key 与value关联的key。如果为nil,则此方法无效。
*/
- (void)removeObjectForKey:(id)key;
/**
清空所有对象
*/
- (void)removeAllObjects;
#pragma mark - Trim
///=============================================================================
/// @name Trim
///=============================================================================
/**
使用LRU从缓存中删除对象,直到“totalCount”小于或等于指定的值。
@param count 修剪缓存后允许保留的总数量。
*/
- (void)trimToCount:(NSUInteger)count;
/**
使用LRU从缓存中删除对象,直到“totalCost”等于或等于指定的值。
@param cost 修剪缓存后允许保留的总开销。
*/
- (void)trimToCost:(NSUInteger)cost;
/**
使用LRU从缓存中移除对象,直到所有过期对象被指定value。
@param age 对象的最大年龄(秒)。
*/
- (void)trimToAge:(NSTimeInterval)age;
@end
NS_ASSUME_NONNULL_END
YYMemoryCache.m
//
// YYMemoryCache.m
// YYKit <https://github.com/ibireme/YYKit>
//
// Created by ibireme on 15/2/7.
// Copyright (c) 2015 ibireme.
//
// This source code is licensed under the MIT-style license found in the
// LICENSE file in the root directory of this source tree.
//
#import "YYMemoryCache.h"
#import <UIKit/UIKit.h>
#import <CoreFoundation/CoreFoundation.h>
#import <QuartzCore/QuartzCore.h>
#import <pthread.h>
#if __has_include("YYDispatchQueuePool.h")
#import "YYDispatchQueuePool.h"
#endif
#ifdef YYDispatchQueuePool_h
// YYMemoryCache获取释放队列
static inline dispatch_queue_t YYMemoryCacheGetReleaseQueue() {
return YYDispatchQueueGetForQOS(NSQualityOfServiceUtility);
}
#else
// YYMemoryCache获取释放队列
static inline dispatch_queue_t YYMemoryCacheGetReleaseQueue() {
return dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);
}
#endif
/**
双向链表中的节点。
通常,不应直接使用此类。
*/
@interface _YYLinkedMapNode : NSObject {
@package
__unsafe_unretained _YYLinkedMapNode *_prev; // 由dic保留 上一个节点
__unsafe_unretained _YYLinkedMapNode *_next; // 由dic保留 下一个节点
id _key; // 该节点的key
id _value; // 该节点的value
NSUInteger _cost; // 占用空间
NSTimeInterval _time; // 时间
}
@end
@implementation _YYLinkedMapNode
@end
/**
YYMemoryCache使用的双向链表, 头节点的 prev节点为 nil , 尾节点的 next 节点为nil
它不是线程安全的,也不验证参数.
通常,不应直接使用此类。
*/
@interface _YYLinkedMap : NSObject {
@package
CFMutableDictionaryRef _dic; // 可变字典 不要直接设置对象
NSUInteger _totalCost; // 总占用
NSUInteger _totalCount; // 总数量
_YYLinkedMapNode *_head; // 头部 MRU,不要直接更改
_YYLinkedMapNode *_tail; // 尾部 LRU,不要直接更改
BOOL _releaseOnMainThread; // 在主线程释放
BOOL _releaseAsynchronously; // 异步释放
}
/// 在头部插入一个节点并更新总占用。
/// 节点和节点的key不应该是nil。
- (void)insertNodeAtHead:(_YYLinkedMapNode *)node;
///将已存在的节点移动到头节点。
/// 节点应该已经在字典内。
- (void)bringNodeToHead:(_YYLinkedMapNode *)node;
/// 删除内部节点并更新总占用。
/// 节点应该已经在字典内。
- (void)removeNode:(_YYLinkedMapNode *)node;
/// 删除尾部节点(如果存在), 并返回删除的节点
- (_YYLinkedMapNode *)removeTailNode;
/// 在后台线程删除所有节点
- (void)removeAll;
@end
@implementation _YYLinkedMap
#pragma mark 初始化
- (instancetype)init {
self = [super init];
// 创建一个新的可变词典 参数: 1.分配默认的空间 2.可以包含的最大键/值对数目(0为无限大) 3.指向使用回调初始化的结构的指针,该回调用于保留,释放,描述和比较字典中的 key 4.指向使用回调初始化的结构的指针,该回调用于保留,释放,描述和比较字典中的 value
_dic = CFDictionaryCreateMutable(CFAllocatorGetDefault(), 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
_releaseOnMainThread = NO; // 不在主线程释放
_releaseAsynchronously = YES; // 在子线程释放
return self;
}
#pragma mark 销毁时
- (void)dealloc {
// 手动销毁字典
CFRelease(_dic);
}
#pragma mark 在头部插入一个节点并更新总占用。
- (void)insertNodeAtHead:(_YYLinkedMapNode *)node {
// 设置可变字典的key 和 value 参数: 1.可变字典 2. 该节点的key(使用__bridge 将对象转为c语言指针) 3.该节点
CFDictionarySetValue(_dic, (__bridge const void *)(node->_key), (__bridge const void *)(node));
// 总占用空间 累加节点的占用空间
_totalCost += node->_cost;
// 总数量加1
_totalCount++;
// 如果头节点有值
if (_head) {
// 将新节点的next指向头结点
node->_next = _head;
// 将头节点的prev指向新节点
_head->_prev = node;
// 设置新节点为头节点
_head = node;
} else { // 头结点没值
// 将头结点,尾节点 设置为新节点
_head = _tail = node;
}
}
#pragma mark 将已存在的节点移动到头节点。
- (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;
}
#pragma mark 删除内部节点并更新总占用。
- (void)removeNode:(_YYLinkedMapNode *)node {
// 删除字典中的value 参数: 1.可变字典 2. 该节点的key
CFDictionaryRemoveValue(_dic, (__bridge const void *)(node->_key));
// 在总占用中减去该节点的占用空间
_totalCost -= node->_cost;
// 在总节点数中减去1
_totalCount--;
// 如果该节点有next节点, 则设置该节点的next节点的prev 指向该节点的prev
if (node->_next) node->_next->_prev = node->_prev;
// 如果该节点有 prev,则设置该节点的prev节点的next, 指向该节点的next
if (node->_prev) node->_prev->_next = node->_next;
// 如果该节点为头节点, 则将该节点的next节点标记为头节点
if (_head == node) _head = node->_next;
// 如果该节点为尾部节点, 则将该节点的prev节点设置为尾节点
if (_tail == node) _tail = node->_prev;
}
#pragma mark 删除尾部节点(如果存在), 并返回删除的节点
- (_YYLinkedMapNode *)removeTailNode {
// 如果没有尾部节点, 则返回
if (!_tail) return nil;
// 拿到尾节点
_YYLinkedMapNode *tail = _tail;
// 在可变字典中删除尾节点 参数: 1. 可变字典 2. 尾节点的key
CFDictionaryRemoveValue(_dic, (__bridge const void *)(_tail->_key));
// 总占用减去尾节点的占用空间
_totalCost -= _tail->_cost;
// 总节点数量减去1
_totalCount--;
// 如果头节点 == 尾节点
if (_head == _tail) {
// 则设置头节点与尾节点都为nil
_head = _tail = nil;
} else {
// 如果头头结点不是尾节点
// 则 将尾节点的prev节点标识为新的尾节点
_tail = _tail->_prev;
// 将尾节点的next节点设置为nil
_tail->_next = nil;
}
// 返回尾节点
return tail;
}
#pragma mark 删除所有节点
- (void)removeAll {
// 设置总占用为0
_totalCost = 0;
// 设置总数量为0
_totalCount = 0;
// 设置头结点为nil
_head = nil;
// 设置尾节点为nil
_tail = nil;
//获取可变字典的count, 如果大于0
if (CFDictionaryGetCount(_dic) > 0) {
// 将可变字典赋值给 holder
CFMutableDictionaryRef holder = _dic;
// 创建可变字典 ---------- 为可变字典重新赋值
_dic = CFDictionaryCreateMutable(CFAllocatorGetDefault(), 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
// 如果设置为在子线程释放(默认为YES)
if (_releaseAsynchronously) {
// 获取线程 如果设置在主线程释放,则拿到主队列 否则拿到缓存池队列
dispatch_queue_t queue = _releaseOnMainThread ? dispatch_get_main_queue() : YYMemoryCacheGetReleaseQueue();
// 通过队列释放原字典
dispatch_async(queue, ^{
CFRelease(holder); // 在指定队列中保持和释放
});
} else if (_releaseOnMainThread && !pthread_main_np()) {
// 如果设置为在主队列释放,则获取主线程释放
dispatch_async(dispatch_get_main_queue(), ^{
CFRelease(holder); // 在指定队列中保持和释放
});
} else {
// 否则在当前线程释放(主线程或子线程都有可能)
CFRelease(holder);
}
}
}
@end
#pragma mark YYMemoryCache
// 注意区分 以_开头与没有_的方法
@implementation YYMemoryCache {
pthread_mutex_t _lock; // 互斥锁
_YYLinkedMap *_lru; // LRU 链表
dispatch_queue_t _queue; // 线程队列
}
#pragma mark 递归修剪
- (void)_trimRecursively {
// 设置 weak self
__weak typeof(self) _self = self;
// 延时调用 参数: 1. _autoTrimInterval * NSEC_PER_SEC (自动微调时间 * 1秒) 2. 在全局子线程中,调度队列优先级低
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];
});
}
#pragma mark 在后台线程修剪
- (void)_trimInBackground {
// 使用线程队列
dispatch_async(_queue, ^{
// 修剪总占用 参数: 自己可以保留的最大总开销
[self _trimToCost:self->_costLimit];
// 修剪总数量 参数: 缓存可以存储的最大对象数
[self _trimToCount:self->_countLimit];
// 修剪过期时间 参数: 缓存中对象的最长过期时间
[self _trimToAge:self->_ageLimit];
});
}
#pragma mark 修剪总开销
- (void)_trimToCost:(NSUInteger)costLimit {
// 是否完成修剪
BOOL finish = NO;
// 添加互斥锁 ++++++++++++++++++
pthread_mutex_lock(&_lock);
// 如果最大总开销为0
if (costLimit == 0) {
// 则删除 LRU 内所有节点
[_lru removeAll];
// 完成修剪
finish = YES;
} else if (_lru->_totalCost <= costLimit) {
// 如果 LRU 内总开销 小于等于 设置的最大总开销
// 则 完成修剪
finish = YES;
}
// 移除互斥锁 ------------------
pthread_mutex_unlock(&_lock);
// 如果已经完成修剪,则返回。 如果最大开销值不为0 并且LRU的总开销大于最大开销值,则继续进行修剪
if (finish) return;
// 创建一个可变数组 holder
NSMutableArray *holder = [NSMutableArray new];
// 循环判断,直到完成修剪
while (!finish) {
// 尝试加锁,如果 == 0 则加锁成功, 否则当前锁正在使用中,休眠10毫秒
if (pthread_mutex_trylock(&_lock) == 0) {
// 如果LRU的总开销 > 最大开销值
if (_lru->_totalCost > costLimit) {
// LRU删除尾部节点
_YYLinkedMapNode *node = [_lru removeTailNode];
// 如果节点不为空,则添加到可变数组中
if (node) [holder addObject:node];
} else {
// LRU的总开销 <= 最大开销值
// 完成开销
finish = YES;
}
// 移除互斥锁 -------------------
pthread_mutex_unlock(&_lock);
} else {
// 当前锁正在使用中,休眠10毫秒
usleep(10 * 1000); //10 ms
}
}
// 如果可变数组有值
if (holder.count) {
// 获取主队列 或 缓存池队列
dispatch_queue_t queue = _lru->_releaseOnMainThread ? dispatch_get_main_queue() : YYMemoryCacheGetReleaseQueue();
dispatch_async(queue, ^{
[holder count]; // 在队列中释放
});
}
}
#pragma mark 修剪总数量
- (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); //10 ms
}
}
if (holder.count) {
dispatch_queue_t queue = _lru->_releaseOnMainThread ? dispatch_get_main_queue() : YYMemoryCacheGetReleaseQueue();
dispatch_async(queue, ^{
[holder count]; // release in queue
});
}
}
#pragma mark 修剪过期时间
- (void)_trimToAge:(NSTimeInterval)ageLimit {
// 是否完成修剪
BOOL finish = NO;
// 返回当前的绝对时间,以秒为单位
NSTimeInterval now = CACurrentMediaTime();
// 添加互斥锁 +++++++++++++++++++++
pthread_mutex_lock(&_lock);
// 如果最长过期时间为0
if (ageLimit <= 0) {
// LRU 删除所有节点
[_lru removeAll];
// 完成修剪
finish = YES;
} else if (!_lru->_tail || (now - _lru->_tail->_time) <= ageLimit) {
// 如果 LRU的尾节点为空 或者 当前时间减去 LRU 最后一个节点的时间 <= 最长过期时间
// 标识完成
finish = YES;
}
// 移除互斥锁
pthread_mutex_unlock(&_lock);
// 如果没有完成修剪
if (finish) return;
// 创建可变数组
NSMutableArray *holder = [NSMutableArray new];
// 循环判断是否完成
while (!finish) {
// 尝试加锁,如果加锁失败则睡眠 10 毫秒
if (pthread_mutex_trylock(&_lock) == 0) {
// 如果LRU的尾结点不为空 并且 当前时间减去 LRU尾节点的时间 > 最长过期时间
if (_lru->_tail && (now - _lru->_tail->_time) > ageLimit) {
// 则 LRU删除尾节点
_YYLinkedMapNode *node = [_lru removeTailNode];
// 如果尾节点不为空,则添加到可变数组中
if (node) [holder addObject:node];
} else {
// 否则完成修剪
finish = YES;
}
// 移除互斥锁 ------------------
pthread_mutex_unlock(&_lock);
} else {
// 睡眠 10 毫秒
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
});
}
}
#pragma mark 接收内存警告通知
- (void)_appDidReceiveMemoryWarningNotification {
// 调用内存警告block
if (self.didReceiveMemoryWarningBlock) {
self.didReceiveMemoryWarningBlock(self);
}
// 如果设置收到通知后清空所有对象
if (self.shouldRemoveAllObjectsOnMemoryWarning) {
// 则删除所有对象
[self removeAllObjects];
}
}
#pragma mark 接收到应用已经进入后台的通知
- (void)_appDidEnterBackgroundNotification {
// 调用进入后台block
if (self.didEnterBackgroundBlock) {
self.didEnterBackgroundBlock(self);
}
// 如果设置收到通知后清空所有对象
if (self.shouldRemoveAllObjectsWhenEnteringBackground) {
// 则删除所有对象
[self removeAllObjects];
}
}
#pragma mark - public
#pragma mark 初始化
- (instancetype)init {
self = super.init;
// 初始化互斥锁
pthread_mutex_init(&_lock, NULL);
// 初始化 LRU 链表
_lru = [_YYLinkedMap new];
// 创建串行队列
_queue = dispatch_queue_create("com.ibireme.cache.memory", DISPATCH_QUEUE_SERIAL);
// 设置默认最大数量
_countLimit = NSUIntegerMax;
// 设置默认最大开销占用
_costLimit = NSUIntegerMax;
// 设置默认最长过期时间
_ageLimit = DBL_MAX;
// 设置自动清理时间为 5 秒
_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;
}
#pragma mark 销毁时
- (void)dealloc {
// 移除通知
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidEnterBackgroundNotification object:nil];
// 清空 LRU
[_lru removeAll];
// 销毁互斥锁
pthread_mutex_destroy(&_lock);
}
/// 设置最大数量
- (NSUInteger)totalCount {
// 加锁
pthread_mutex_lock(&_lock);
// 设置 LRU 最大节点数量
NSUInteger count = _lru->_totalCount;
// 移除锁
pthread_mutex_unlock(&_lock);
return count;
}
/// 设置最大开销占用
- (NSUInteger)totalCost {
// 加锁
pthread_mutex_lock(&_lock);
// 设置 LRU 最大开销占用
NSUInteger totalCost = _lru->_totalCost;
// 移除锁
pthread_mutex_unlock(&_lock);
return totalCost;
}
/// 在主线程上释放
- (BOOL)releaseOnMainThread {
// 加锁
pthread_mutex_lock(&_lock);
// 获取 LRU 是否开启在主线程释放
BOOL releaseOnMainThread = _lru->_releaseOnMainThread;
// 移除锁
pthread_mutex_unlock(&_lock);
return releaseOnMainThread;
}
/// 设置是否在主线程上释放
- (void)setReleaseOnMainThread:(BOOL)releaseOnMainThread {
// 加锁
pthread_mutex_lock(&_lock);
// 设置LRU是否在主线程释放
_lru->_releaseOnMainThread = releaseOnMainThread;
// 移除锁
pthread_mutex_unlock(&_lock);
}
/// 获取是否异步释放
- (BOOL)releaseAsynchronously {
// 加锁
pthread_mutex_lock(&_lock);
// 获取是否异步释放
BOOL releaseAsynchronously = _lru->_releaseAsynchronously;
// 移除锁
pthread_mutex_unlock(&_lock);
return releaseAsynchronously;
}
/// 设置是否异步释放
- (void)setReleaseAsynchronously:(BOOL)releaseAsynchronously {
pthread_mutex_lock(&_lock);
_lru->_releaseAsynchronously = releaseAsynchronously;
pthread_mutex_unlock(&_lock);
}
#pragma mark 返回一个布尔值,该值指示给定的key是否在缓存中。
- (BOOL)containsObjectForKey:(id)key {
// 如果 key 为 nil 返回 false
if (!key) return NO;
// 加锁
pthread_mutex_lock(&_lock);
// 通过 CFDictionary 判断 LRU 中的 可变字典 是否包含该 key
BOOL contains = CFDictionaryContainsKey(_lru->_dic, (__bridge const void *)(key));
// 移除锁
pthread_mutex_unlock(&_lock);
return contains;
}
#pragma mark 返回与给定key关联的value。
- (id)objectForKey:(id)key {
// 如果 key 为 nil 返回 false
if (!key) return nil;
// 加锁
pthread_mutex_lock(&_lock);
// 通过 CFDictionary 获取该 key 所对应的 节点
_YYLinkedMapNode *node = CFDictionaryGetValue(_lru->_dic, (__bridge const void *)(key));
// 如果 node 不为 nil
if (node) {
// 设置该 节点的 time 为当前时间
node->_time = CACurrentMediaTime();
// LRU 将该节点移到头节点
[_lru bringNodeToHead:node];
}
// 移除锁
pthread_mutex_unlock(&_lock);
// 返回 node 所对应的 value ,如果没有则 返回 nil
return node ? node->_value : nil;
}
#pragma mark 设置缓存中指定key的value(0)。
- (void)setObject:(id)object forKey:(id)key {
// 调用设设置缓存中指定key的value 并设置占用空间。的方法
[self setObject:object forKey:key withCost:0];
}
#pragma mark 设置缓存中指定key的value 并设置占用空间。
- (void)setObject:(id)object forKey:(id)key withCost:(NSUInteger)cost {
// 如果 key 为nil 则直接返回
if (!key) return;
if (!object) {
// 如果 对象为 nil 则删除该 key 所对应的对象, 并返回
[self removeObjectForKey:key];
return;
}
// 加锁 ++++++++++++++
pthread_mutex_lock(&_lock);
// 使用 CFDictionary 通过 key 获取 node节点
_YYLinkedMapNode *node = CFDictionaryGetValue(_lru->_dic, (__bridge const void *)(key));
// 获取当前时间
NSTimeInterval now = CACurrentMediaTime();
// 如果 node 不为 nil ,则重置node
if (node) {
// LRU 的 占用空间 减去 原nodel 的占用空间
_lru->_totalCost -= node->_cost;
// LRU 的 占用空间 加上新 node 的占用空间
_lru->_totalCost += cost;
// 设置原 node 的占用空间为 新的占用空间
node->_cost = cost;
// 设置原 node 的time 为 当前时间
node->_time = now;
// 设置原node 的 value 为 object
node->_value = object;
// 并将 该节点 移到顶部
[_lru bringNodeToHead:node];
} else {
// 否则,字典中没有该 Node , 需要添加新的 node
// 初始化一个 新node
node = [_YYLinkedMapNode new];
// 设置它的 占用空间,时间, key 以及 value(object)
node->_cost = cost;
node->_time = now;
node->_key = key;
node->_value = object;
// 将该node 插入到链表顶部
[_lru insertNodeAtHead:node];
}
// 如果 LRU链表的 占用空间 > 最大占用空间
if (_lru->_totalCost > _costLimit) {
// 通过队列,修剪占用空间
dispatch_async(_queue, ^{
[self trimToCost:_costLimit];
});
}
// 如果 LRU链表的最大数量 > 最大对象数
if (_lru->_totalCount > _countLimit) {
// LRU 移除尾部的node
_YYLinkedMapNode *node = [_lru removeTailNode];
// 如果LRU 设置了异步释放
if (_lru->_releaseAsynchronously) {
// 则获取对应的线程队列
dispatch_queue_t queue = _lru->_releaseOnMainThread ? dispatch_get_main_queue() : YYMemoryCacheGetReleaseQueue();
dispatch_async(queue, ^{
[node class]; //在队列中保持和释放
});
} else if (_lru->_releaseOnMainThread && !pthread_main_np()) {
// 如果设置为在主队列释放,则获取主线程释放
dispatch_async(dispatch_get_main_queue(), ^{
[node class]; //在队列中保持和释放
});
}
}
// 移除锁
pthread_mutex_unlock(&_lock);
}
#pragma mark 删除缓存中指定key的value。
- (void)removeObjectForKey:(id)key {
// 如果key 为nil 则返回
if (!key) return;
// 加锁
pthread_mutex_lock(&_lock);
// 通过 CFDictionary 获取 key 对应的 value (node)
_YYLinkedMapNode *node = CFDictionaryGetValue(_lru->_dic, (__bridge const void *)(key));
// 如果 node 存在
if (node) {
// LRU 移除该 Node
[_lru removeNode:node];
// 释放该 node
if (_lru->_releaseAsynchronously) {
dispatch_queue_t queue = _lru->_releaseOnMainThread ? dispatch_get_main_queue() : YYMemoryCacheGetReleaseQueue();
dispatch_async(queue, ^{
[node class]; //在队列中保持和释放
});
} else if (_lru->_releaseOnMainThread && !pthread_main_np()) {
dispatch_async(dispatch_get_main_queue(), ^{
[node class]; //在队列中保持和释放
});
}
}
// 移除锁
pthread_mutex_unlock(&_lock);
}
#pragma mark 清空所有对象
- (void)removeAllObjects {
// 加锁
pthread_mutex_lock(&_lock);
// 操作LRU 移除所有对象
[_lru removeAll];
// 移除锁
pthread_mutex_unlock(&_lock);
}
#pragma mark 使用LRU从缓存中删除对象,直到“totalCount”小于或等于指定的值。
- (void)trimToCount:(NSUInteger)count {
if (count == 0) {
// 如果 为 0 则删除所有对象
[self removeAllObjects];
return;
}
// 修剪总数量
[self _trimToCount:count];
}
#pragma mark 使用LRU从缓存中删除对象,直到“totalCost”等于或等于指定的值。
- (void)trimToCost:(NSUInteger)cost {
// 修剪总占用大小
[self _trimToCost:cost];
}
#pragma mark 使用LRU从缓存中移除对象,直到所有过期对象被指定value。
- (void)trimToAge:(NSTimeInterval)age {
// 修剪所有过期对象
[self _trimToAge:age];
}
/// 打印信息
- (NSString *)description {
if (_name) return [NSString stringWithFormat:@"<%@: %p> (%@)", self.class, self, _name];
else return [NSString stringWithFormat:@"<%@: %p>", self.class, self];
}
@end
没有YYDiskCache部分是因为我对数据库代码操作部分不太懂,有兴趣的可以自己去学习一下