落魄的iOS开发

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部分是因为我对数据库代码操作部分不太懂,有兴趣的可以自己去学习一下

上一篇 下一篇

猜你喜欢

热点阅读