读 SDWebImage 三 (SDImageCache)
2018-10-02 10:38:36 # SDWebImage

1、介绍

SDImageCacheSDWebImage 处理图片缓存的类。图片的存储是针对内存和磁盘有一点区别:内存中直接存储图片,磁盘中存中 imagedata
SDWebImageManager 中使用的存储图片、判断图片是否已存储在磁盘/内存等方法均是调用该类中的方法。SDWebImage 设计时做了很好的分工,以至于去分析代码都带有一种享受感。

简单做个思考,如果自己实现一个缓存类,需要做哪些东西:
1、初始化、缓存地址。
3、查询、删除、存储方法(增删改查功能)
4、计算缓存大小
5、计算缓存数量…

然后在接下来的分析中看看跟自己的思路的偏差:

SDImageCache.h 文件中引用了 SDWebImageCompatSDImageCacheConfig 头文件。SDWebImageCompat 类在 SDWebImageManager 结尾已经分析过,该类只包含一个方法,用来实现图片缩放的操作。

SDImageCacheConfig 类则是管理缓存配置信息的,这里先单独拉出来看下

2、SDImageCacheConfig

SDImageCacheConfig 类是用于配置缓存信息的,继承自 NSObject

2.1、.h文件

缓存配置过期类型,枚举

1
2
3
4
5
6
typedef NS_ENUM(NSUInteger, SDImageCacheConfigExpireType) {
//访问图片时,它将更新此值 (访问日期)
SDImageCacheConfigExpireTypeAccessDate,
//图片从磁盘缓存中获取 (修改日期)
SDImageCacheConfigExpireTypeModificationDate
};

定义配置属性如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
/**
* 解压缩下载和缓存的图片可以提高性能,但会占用大量内存。
* 默认为YES。 如果由于过多的内存消耗而遇到崩溃,请将此项设置为NO。
*/
//是否解压图片,默认YES
@property (assign, nonatomic) BOOL shouldDecompressImages;

/**
* 是否禁用iCloud备份,默认YES
*/
@property (assign, nonatomic) BOOL shouldDisableiCloud;

/**
* 是否使用内存缓存,默认YES
* 禁用内存缓存时,也会禁用弱内存缓存。
*/
@property (assign, nonatomic) BOOL shouldCacheImagesInMemory;

/**
* 控制图片的弱内存缓存的选项.启用时, SDImageCache 的内存缓存将使用弱映射表在存储到内存的同时存储图像,并同时删除.
* 但是当触发内存警告时,由于弱映射表没有强烈的图像实例引用,即使内存缓存本身被清除,UIImageViews或其他实时实例强烈保留的一些图像也可以再次恢复,以避免 稍后从磁盘缓存或网络重新查询。 这可能对这种情况有所帮助,例如,当app进入后台并清除内存时,会在重新输入前景后导致单元格闪烁。
* 默认为YES。 您可以动态更改此选项。
*/
//是否使用弱内存缓存,默认为YES
@property (assign, nonatomic) BOOL shouldUseWeakMemoryCache;

/**
* 从磁盘读取缓存时的读取选项
* 默认为 0. 可以设置为 `NSDataReadingMappedIfSafe` 以提高性能.
*/
//磁盘缓存读取选项,枚举
@property (assign, nonatomic) NSDataReadingOptions diskCacheReadingOptions;

/**
* 将缓存写入磁盘时的写入选项
* 默认为 NSDataWritingAtomic. 可以将其设置为 `NSDataWritingWithoutOverwriting` 以防止覆盖现有文件
*/
//磁盘缓存写入选项,枚举
@property (assign, nonatomic) NSDataWritingOptions diskCacheWritingOptions;

/**
* 在缓存中保留图片的最长时间,秒为单位
*/
@property (assign, nonatomic) NSInteger maxCacheAge;

/**
* 缓存的最大值,字节为单位,默认为0,表示不做限制
*/
@property (assign, nonatomic) NSUInteger maxCacheSize;

/**
* 清理磁盘缓存时将检查清理缓存的属性
* 默认修改日期
*/
//缓存配置过期类型,枚举 ,默认修改日期
@property (assign, nonatomic) SDImageCacheConfigExpireType diskCacheExpireType;

2.2、.m文件

静态不可变 NSInteger 类型的 kDefaultCacheMaxCacheAge 表示在缓存中图像保存时间的最大长度,以秒为单位 默认是一周时间(60 * 60 * 24 * 7)。
_maxCacheAge 属性在 .h 中声明,可以外部修改。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
static const NSInteger kDefaultCacheMaxCacheAge = 60 * 60 * 24 * 7; // 1 week

//属性初始化赋值
- (instancetype)init {
if (self = [super init]) {
_shouldDecompressImages = YES;
_shouldDisableiCloud = YES;
_shouldCacheImagesInMemory = YES;
_shouldUseWeakMemoryCache = YES;
_diskCacheReadingOptions = 0;
_diskCacheWritingOptions = NSDataWritingAtomic;
_maxCacheAge = kDefaultCacheMaxCacheAge;
_maxCacheSize = 0;
_diskCacheExpireType = SDImageCacheConfigExpireTypeModificationDate;
}
return self;
}

3、SDImageCache

SDImageCache 维护内存缓存和可选的磁盘缓存。磁盘缓存写入操作是异步执行的,因此不会给UI增加不必要的延迟。

3.1、.h文件

缓存类型,枚举

1
2
3
4
5
6
7
8
9
10
11
12
13
14
typedef NS_ENUM(NSInteger, SDImageCacheType) {
/**
* 图像不能用 SDWebImage 缓存,但能从网上下载 (不缓存)。
*/
SDImageCacheTypeNone,
/**
* 图片从磁盘中获取(缓存到磁盘中)
*/
SDImageCacheTypeDisk,
/**
*图片从内存中获取(缓存到内存中)
*/
SDImageCacheTypeMemory
};

缓存选项,枚举

1
2
3
4
5
6
7
8
9
10
11
12
13
14
typedef NS_OPTIONS(NSUInteger, SDImageCacheOptions) {
/**
* 默认情况下,当图像缓存在内存中时,我们不查询磁盘数据。 此选项可以强制同时查询磁盘数据。
*/
SDImageCacheQueryDataWhenInMemory = 1 << 0,
/**
* 默认情况下,我们同步查询内存缓存,异步查询磁盘缓存。 此选项可以强制同步查询磁盘缓存。
*/
SDImageCacheQueryDiskSync = 1 << 1,
/**
* 默认情况下,图像会根据其原始大小进行解码。在iOS上,此选项会将图像缩小到与设备的受限内存兼容的大小。
*/
SDImageCacheScaleDownLargeImages = 1 << 2
};

三个回调代码块

1
2
3
4
5
6
7
8
//查询完成的block
typedef void(^SDCacheQueryCompletedBlock)(UIImage * _Nullable image, NSData * _Nullable data, SDImageCacheType cacheType);

//检查完成的block
typedef void(^SDWebImageCheckCacheCompletionBlock)(BOOL isInCache);

//计算缓存大小的block
typedef void(^SDWebImageCalculateSizeBlock)(NSUInteger fileCount, NSUInteger totalSize);

SDImageCache的属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* 缓存配置对象,存储所有类型的设置
*/
@property (nonatomic, nonnull, readonly) SDImageCacheConfig *config;

/**
* 设置缓存中最大的消耗的内存,这里计算的是内存中的像素个数
*/
@property (assign, nonatomic) NSUInteger maxMemoryCost;

/**
* 缓存应持有的对象的的最大数量。
*/
@property (assign, nonatomic) NSUInteger maxMemoryCountLimit;

SDImageCache的单例和初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* 返回全局共享缓存实例
*
* @return SDImageCache全局实例
*/
+ (nonnull instancetype)sharedImageCache;

/**
* 使用特定命名空间初始化一个新的缓存存储,里面就是去获取磁盘缓存路径,然后在进行一系列的初始化操作
*
* @param ns 用于此缓存存储的命名空间
*/
- (nonnull instancetype)initWithNamespace:(nonnull NSString *)ns;

/**
* 使用特定的命名空间和目录初始化一个新的缓存存储
* @param ns 用于此缓存存储的命名空间
* @param directory 用于缓存磁盘映像的目录
*/
- (nonnull instancetype)initWithNamespace:(nonnull NSString *)ns
diskCacheDirectory:(nonnull NSString *)directory NS_DESIGNATED_INITIALIZER;

缓存路径

1
2
3
4
5
6
7
8
9
10
//初始化磁盘缓存路径
- (nullable NSString *)makeDiskCachePath:(nonnull NSString*)fullNamespace;

/**
* 添加只读缓存路径用来搜索由SDImageCache预先缓存的图片
* 如果想要预先加载的图片和应用程序捆绑在一起,则非常有用。去找图片也可以在这个路径中添加
*
* @param path 此只读缓存路径使用的路径
*/
- (void)addReadOnlyCachePath:(nonnull NSString *)path;

存储操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
/**
* 根据key将图片异步缓存到内存和磁盘中
*
* @param image 需要缓存的图片
* @param key 唯一的缓存图片的key,通常是图像的绝对URL
* @param completionBlock 操作完成后执行的块
*/
- (void)storeImage:(nullable UIImage *)image
forKey:(nullable NSString *)key
completion:(nullable SDWebImageNoParamsBlock)completionBlock;

/**
* 根据key将图片异步缓存到内存和磁盘中
* (根据toDisk来判断是否要存储到磁盘中,这里的磁盘缓存是可选的)
* @param image 需要缓存的图片
* @param key 唯一的缓存图片的key,通常是图像的绝对URL
* @param toDisk 是否缓存到磁盘中
* @param completionBlock 操作完成后执行的块
*/
- (void)storeImage:(nullable UIImage *)image
forKey:(nullable NSString *)key
toDisk:(BOOL)toDisk
completion:(nullable SDWebImageNoParamsBlock)completionBlock;

/**
* 根据key将图片异步缓存到内存和磁盘中
* (这里面的方法会根据imageData如果没有,但是image有的话,就会考虑到图片格式的问题)
* @param image 需要缓存的图片
* @param imageData 服务器返回的图像数据,此表示将用于磁盘存储,而不是将给定的图像对象转换为可存储/压缩的图像格式,以节省质量和CPU
* @param key 唯一的缓存图片的key,通常是图像的绝对URL
* @param toDisk 是否缓存到磁盘中
* @param completionBlock 操作完成后执行的块
*/
- (void)storeImage:(nullable UIImage *)image
imageData:(nullable NSData *)imageData
forKey:(nullable NSString *)key
toDisk:(BOOL)toDisk
completion:(nullable SDWebImageNoParamsBlock)completionBlock;

/**
* 根据key将图片data同步缓存到内存和磁盘中
*
*
* @param imageData 需要缓存的图片data
* @param key 唯一的缓存图片的key,通常是图像的绝对URL
*/
- (void)storeImageDataToDisk:(nullable NSData *)imageData forKey:(nullable NSString *)key;

查询和检索操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
/**
* 异步检查磁盘缓存中是否存在图片(不加载图片),回调返回结果
*
* @param key 描述url的key
* @param completionBlock 检查完成时要执行的块。
* @note 将在主队列上始终执行完成块
*/
- (void)diskImageExistsWithKey:(nullable NSString *)key completion:(nullable SDWebImageCheckCacheCompletionBlock)completionBlock;

/**
* 同步检查磁盘缓存中是否存在图片(不加载图片),直接返回结果
*
* @param key 描述url的key
*/
- (BOOL)diskImageDataExistsWithKey:(nullable NSString *)key;

/**
* 根据key同步查询图片数据data
*
* @param key 用来存储所需图片唯一的key
* @return 根据key返回查找的图片,如果未找到,返回nil
*/
- (nullable NSData *)diskImageDataForKey:(nullable NSString *)key;

/**
* 异步查询缓存并在完成后调用完成的操作。
*
* @param key 用来存储所需图片唯一的key
* @param doneBlock The completion block. 如果操作被取消,则不会被调用
*
* @return 包含缓存操作的NSOperation实例
*/
- (nullable NSOperation *)queryCacheOperationForKey:(nullable NSString *)key done:(nullable SDCacheQueryCompletedBlock)doneBlock;

/**
* 异步查询缓存并在完成后调用完成的操作。
*
* @param key 用来存储所需图片唯一的key
* @param options 用于指定用于此高速缓存查询的选项
* @param doneBlock The completion block. 如果操作被取消,则不会被调用
*
* @return 包含缓存操作的NSOperation实例
*/
- (nullable NSOperation *)queryCacheOperationForKey:(nullable NSString *)key options:(SDImageCacheOptions)options done:(nullable SDCacheQueryCompletedBlock)doneBlock;

/**
* 同步查询内存缓存
*
* @param key 用来存储所需图片唯一的key
* @return 根据key返回查找的图片,如果未找到,返回nil
*/
- (nullable UIImage *)imageFromMemoryCacheForKey:(nullable NSString *)key;

/**
* 同步查询磁盘缓存
*
* @param key 用来存储所需图片唯一的key
* @return 根据key返回查找的图片,如果未找到,返回nil
*/
- (nullable UIImage *)imageFromDiskCacheForKey:(nullable NSString *)key;

/**
* 检查缓存后,同步查询缓存(磁盘或内存)
*
* @param key 用来存储所需图片唯一的key
* @return 根据key返回查找的图片,如果未找到,返回nil
*/
- (nullable UIImage *)imageFromCacheForKey:(nullable NSString *)key;

移除操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* 从内存或者磁盘缓存中异步移除图片
*
* @param key 唯一的图片缓存key
* @param completion 删除图像后应执行的块(可选)
*/
- (void)removeImageForKey:(nullable NSString *)key withCompletion:(nullable SDWebImageNoParamsBlock)completion;

/**
* 从内存和可选磁盘缓存中异步移除图片
*
* @param key 唯一的图片缓存key
* @param fromDisk 是否也从磁盘中移除
* @param completion 删除图像后应执行的块(可选)
*/
- (void)removeImageForKey:(nullable NSString *)key fromDisk:(BOOL)fromDisk withCompletion:(nullable SDWebImageNoParamsBlock)completion;

缓存清理操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* 清理所有的内存缓存图片
*/
- (void)clearMemory;

/**
* 异步清除所有磁盘缓存的图片。 非阻塞方法 - 立即返回。
* @param completion 缓存过期完成后应执行的块(可选)
*/
- (void)clearDiskOnCompletion:(nullable SDWebImageNoParamsBlock)completion;

/**
* 异步从磁盘中删除所有过期的缓存图片。 非阻塞方法 - 立即返回。
* @param completionBlock 缓存过期完成后应执行的块(可选)
*/
- (void)deleteOldFilesWithCompletionBlock:(nullable SDWebImageNoParamsBlock)completionBlock;

缓存信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* 获取磁盘缓存使用的大小
*/
- (NSUInteger)getSize;

/**
* 获取磁盘缓存中的图片数量
*/
- (NSUInteger)getDiskCount;

/**
* 异步计算磁盘缓存的大小。
*/
- (void)calculateSizeWithCompletionBlock:(nullable SDWebImageCalculateSizeBlock)completionBlock;

缓存路径

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* 需要根路径和key来查询文件所在的位置 (需要缓存路径根文件夹)
*
* @param key the key (可以使用cacheKeyForURL从url获取)
* @param path 缓存路径根文件夹
*
* @return 缓存路径
*/
- (nullable NSString *)cachePathForKey:(nullable NSString *)key inPath:(nonnull NSString *)path;

/**
* 根据key获取相应文件的默认的缓存路径
*
* @param key the key (可以使用cacheKeyForURL从url获取)
*
* @return 默认的缓存路径
*/
- (nullable NSString *)defaultCachePathForKey:(nullable NSString *)key;

3.2、.m文件

C语言函数,本质是计算diskImage所要占用的字节数。

1
2
3
4
5
6
7
FOUNDATION_STATIC_INLINE NSUInteger SDCacheCostForImage(UIImage *image) {
#if SD_MAC
return image.size.height * image.size.width;
#elif SD_UIKIT || SD_WATCH
return image.size.height * image.size.width * image.scale * image.scale;
#endif
}

私有

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
// Private
@interface SDMemoryCache <KeyType, ObjectType> ()

@property (nonatomic, strong, nonnull) SDImageCacheConfig *config;
@property (nonatomic, strong, nonnull) NSMapTable<KeyType, ObjectType> *weakCache; // strong-weak cache
@property (nonatomic, strong, nonnull) dispatch_semaphore_t weakCacheLock; // a lock to keep the access to `weakCache` thread-safe

- (instancetype)init NS_UNAVAILABLE;
- (instancetype)initWithConfig:(nonnull SDImageCacheConfig *)config;

@end

@implementation SDMemoryCache

目前这似乎没有用在macOS上(macOS使用虚拟内存,并且在内存警告时不清除缓存)。 所以我们只在iOS / tvOS平台上覆盖。
//但是将来可能会有更多的子类选项和功能。
#if SD_UIKIT

- (void)dealloc {
//移除内存警告通知
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
}

- (instancetype)initWithConfig:(SDImageCacheConfig *)config {
self = [super init];
if (self) {
// 使用存储二级缓存的强弱映射表。 按照NSCache不复制密钥的文档
// 当内存警告,缓存被清除时,这很有用。 但是,图像实例可以由其他实例保留,例如imageViews和alive。
// 在这种情况下,我们可以同步弱缓存,而不需要从磁盘缓存加载
self.weakCache = [[NSMapTable alloc] initWithKeyOptions:NSPointerFunctionsStrongMemory valueOptions:NSPointerFunctionsWeakMemory capacity:0];
self.weakCacheLock = dispatch_semaphore_create(1);
self.config = config;
//添加内粗警告的通知
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(didReceiveMemoryWarning:)
name:UIApplicationDidReceiveMemoryWarningNotification
object:nil];
}
return self;
}

- (void)didReceiveMemoryWarning:(NSNotification *)notification {
//只删除缓存,但保持弱缓存
[super removeAllObjects];
}

// `setObject:forKey:` 只需调用0即可,覆盖这就足够了
- (void)setObject:(id)obj forKey:(id)key cost:(NSUInteger)g {
//调用系统的NSCache方法
[super setObject:obj forKey:key cost:g];
//如果缓存配置不使用弱内存缓存,返回
if (!self.config.shouldUseWeakMemoryCache) {
return;
}
if (key && obj) {
//若果key和obj存在,存储弱缓存
LOCK(self.weakCacheLock);
[self.weakCache setObject:obj forKey:key];
UNLOCK(self.weakCacheLock);
}
}

//通过key获取object
- (id)objectForKey:(id)key {
id obj = [super objectForKey:key];
if (!self.config.shouldUseWeakMemoryCache) {
return obj;
}
if (key && !obj) {
// 若果key存在,obj不存在,存储弱缓存
LOCK(self.weakCacheLock);
obj = [self.weakCache objectForKey:key];
UNLOCK(self.weakCacheLock);
if (obj) {
//同步缓存
NSUInteger cost = 0;
if ([obj isKindOfClass:[UIImage class]]) {
//diskImage所要占用的字节数
cost = SDCacheCostForImage(obj);
}
[super setObject:obj forKey:key cost:cost];
}
}
return obj;
}

//根据key移除对象
- (void)removeObjectForKey:(id)key {
[super removeObjectForKey:key];
//如果缓存配置不使用弱内存缓存,返回
if (!self.config.shouldUseWeakMemoryCache) {
return;
}
if (key) {
// 如果key存在,移除缓存
LOCK(self.weakCacheLock);
[self.weakCache removeObjectForKey:key];
UNLOCK(self.weakCacheLock);
}
}
//移除所有对象
- (void)removeAllObjects {
[super removeAllObjects];
//如果缓存配置不使用弱内存缓存,返回
if (!self.config.shouldUseWeakMemoryCache) {
return;
}
// 手动删除也应该删除弱缓存
LOCK(self.weakCacheLock);
[self.weakCache removeAllObjects];
UNLOCK(self.weakCacheLock);
}

#else
//如果是macos,直接初始化
- (instancetype)initWithConfig:(SDImageCacheConfig *)config {
self = [super init];
return self;
}

#endif

@end

属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@interface SDImageCache ()

#pragma mark - 属性
//内存缓存
@property (strong, nonatomic, nonnull) SDMemoryCache *memCache;
//磁盘缓存路径
@property (strong, nonatomic, nonnull) NSString *diskCachePath;
//自定义路径
@property (strong, nonatomic, nullable) NSMutableArray<NSString *> *customPaths;
@property (strong, nonatomic, nullable) dispatch_queue_t ioQueue;
//文件管理器
@property (strong, nonatomic, nonnull) NSFileManager *fileManager;

@end

初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
#pragma mark - 单例, 初始化, dealloc

+ (nonnull instancetype)sharedImageCache {
static dispatch_once_t once;
static id instance;
dispatch_once(&once, ^{
instance = [self new];
});
return instance;
}

- (instancetype)init {
//初始化,namespace 默认为:default
return [self initWithNamespace:@"default"];
}

- (nonnull instancetype)initWithNamespace:(nonnull NSString *)ns {
//获取磁盘缓存路径,默认的是:~/default (ns为default,拼接到缓存路径的最后面)
NSString *path = [self makeDiskCachePath:ns];
return [self initWithNamespace:ns diskCacheDirectory:path];
}

- (nonnull instancetype)initWithNamespace:(nonnull NSString *)ns
diskCacheDirectory:(nonnull NSString *)directory {
if ((self = [super init])) {
NSString *fullNamespace = [@"com.hackemist.SDWebImageCache." stringByAppendingString:ns];

// 创建IO串行队列
_ioQueue = dispatch_queue_create("com.hackemist.SDWebImageCache", DISPATCH_QUEUE_SERIAL);

//初始化缓存配置
_config = [[SDImageCacheConfig alloc] init];

// 初始化内存缓存
_memCache = [[SDMemoryCache alloc] initWithConfig:_config];
_memCache.name = fullNamespace;

// 初始化磁盘缓存
if (directory != nil) {
//如果路径不为nil,在路径的结尾拼接fullNamespace
_diskCachePath = [directory stringByAppendingPathComponent:fullNamespace];
} else {
//如果路径为nil,获取路径
NSString *path = [self makeDiskCachePath:ns];
_diskCachePath = path;
}

dispatch_sync(_ioQueue, ^{
//初始化文件管理器
self.fileManager = [NSFileManager new];
});

#if SD_UIKIT
//添加删除通知
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(deleteOldFiles)
name:UIApplicationWillTerminateNotification
object:nil];

[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(backgroundDeleteOldFiles)
name:UIApplicationDidEnterBackgroundNotification
object:nil];
#endif
}

return self;
}

- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
}

缓存路径

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
//添加只读缓存路径用来搜索由SDImageCache预先缓存的图片
- (void)addReadOnlyCachePath:(nonnull NSString *)path {
if (!self.customPaths) {
self.customPaths = [NSMutableArray new];
}
//不包含就添加
if (![self.customPaths containsObject:path]) {
[self.customPaths addObject:path];
}
}

//需要根路径和key来查询文件所在的位置 (需要缓存路径根文件夹)
- (nullable NSString *)cachePathForKey:(nullable NSString *)key inPath:(nonnull NSString *)path {
//根据key获取缓存文件名
NSString *filename = [self cachedFileNameForKey:key];
return [path stringByAppendingPathComponent:filename];
}

//根据key获取默认缓存路径
- (nullable NSString *)defaultCachePathForKey:(nullable NSString *)key {
return [self cachePathForKey:key inPath:self.diskCachePath];
}

//根据key获取缓存文件名
- (nullable NSString *)cachedFileNameForKey:(nullable NSString *)key {
const char *str = key.UTF8String;
if (str == NULL) {
str = "";
}
unsigned char r[CC_MD5_DIGEST_LENGTH];
CC_MD5(str, (CC_LONG)strlen(str), r);
NSURL *keyURL = [NSURL URLWithString:key];
NSString *ext = keyURL ? keyURL.pathExtension : key.pathExtension;
NSString *filename = [NSString stringWithFormat:@"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%@",
r[0], r[1], r[2], r[3], r[4], r[5], r[6], r[7], r[8], r[9], r[10],
r[11], r[12], r[13], r[14], r[15], ext.length == 0 ? @"" : [NSString stringWithFormat:@".%@", ext]];
return filename;
}

//根据fullNamespace获取磁盘缓存路径
- (nullable NSString *)makeDiskCachePath:(nonnull NSString*)fullNamespace {
NSArray<NSString *> *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
return [paths[0] stringByAppendingPathComponent:fullNamespace];
}

存储操作

根据key将图片异步缓存到内存和磁盘中(默认存到内存和磁盘)

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* 根据key将图片异步缓存到内存和磁盘中
*
* @param image 需要缓存的图片
* @param key 唯一的缓存图片的key,通常是图像的绝对URL
* @param completionBlock 操作完成后执行的块
*/

- (void)storeImage:(nullable UIImage *)image
forKey:(nullable NSString *)key
completion:(nullable SDWebImageNoParamsBlock)completionBlock {
[self storeImage:image imageData:nil forKey:key toDisk:YES completion:completionBlock];
}

根据key将图片异步缓存到内存和磁盘中 (默认存储到内存,根据判断是否存储到磁盘中)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* 根据key将图片异步缓存到内存和磁盘中
* (根据toDisk来判断是否要存储到磁盘中,这里的磁盘缓存是可选的)
* @param image 需要缓存的图片
* @param key 唯一的缓存图片的key,通常是图像的绝对URL
* @param toDisk 是否缓存到磁盘中
* @param completionBlock 操作完成后执行的块
*/
- (void)storeImage:(nullable UIImage *)image
forKey:(nullable NSString *)key
toDisk:(BOOL)toDisk
completion:(nullable SDWebImageNoParamsBlock)completionBlock {
[self storeImage:image imageData:nil forKey:key toDisk:toDisk completion:completionBlock];
}

根据key将图片异步缓存到内存和磁盘中 (默认存储到内存,根据判断是否存储到磁盘)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
/**
* 根据key将图片异步缓存到内存和磁盘中
* (这里面的方法会根据imageData如果没有,但是image有的话,就会考虑到图片格式的问题)
* @param image 需要缓存的图片
* @param imageData 服务器返回的图像数据,此表示将用于磁盘存储,而不是将给定的图像对象转换为可存储/压缩的图像格式,以节省质量和CPU
* @param key 唯一的缓存图片的key,通常是图像的绝对URL
* @param toDisk 是否缓存到磁盘中
* @param completionBlock 操作完成后执行的块 typedef void(^SDWebImageNoParamsBlock)(void); 不需要传任何参数
*/

- (void)storeImage:(nullable UIImage *)image
imageData:(nullable NSData *)imageData
forKey:(nullable NSString *)key
toDisk:(BOOL)toDisk
completion:(nullable SDWebImageNoParamsBlock)completionBlock {
//若图片或者key不存在,则不存储,执行回调,返回
if (!image || !key) {
if (completionBlock) {
completionBlock();
}
return;
}
// 如果启用了内存缓存
if (self.config.shouldCacheImagesInMemory) {
NSUInteger cost = SDCacheCostForImage(image);
//根据key缓存image,
[self.memCache setObject:image forKey:key cost:cost];
}

//若需要缓存到磁盘
if (toDisk) {
//异步执行缓存操作
dispatch_async(self.ioQueue, ^{
@autoreleasepool { //自动释放池(里面创建了很多临时变量,当@autoreleasepool结束时,里面的内存就会回收)
NSData *data = imageData;
if (!data && image) {
// 如果我们没有任何数据来检测图像格式,请检查它是否包含使用PNG或JPEG格式的Alpha通道
SDImageFormat format;
if (SDCGImageRefContainsAlpha(image.CGImage)) {
format = SDImageFormatPNG;
} else {
format = SDImageFormatJPEG;
}
将图片编码为图片数据,该方法在SDWebImageCoder类中
data = [[SDWebImageCodersManager sharedInstance] encodedDataWithImage:image format:format];
}
//根据key存储imageData
[self _storeImageDataToDisk:data forKey:key];
}

//如果需要回调,在主线程执行回调
if (completionBlock) {
dispatch_async(dispatch_get_main_queue(), ^{
completionBlock();
});
}
});
} else {
//如果不存储磁盘,执行完成回调
if (completionBlock) {
completionBlock();
}
}
}

根据key将图片data同步缓存到磁盘中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* 根据key将图片data同步缓存到内存和磁盘中
*
* @param imageData 需要缓存的图片data
* @param key 唯一的缓存图片的key,通常是图像的绝对URL
*/

- (void)storeImageDataToDisk:(nullable NSData *)imageData forKey:(nullable NSString *)key {
//若图片或者key不存在,则不能存储
if (!imageData || !key) {
return;
}
dispatch_sync(self.ioQueue, ^{
[self _storeImageDataToDisk:imageData forKey:key];
});
}

根据key将图片data同步缓存到内存和磁盘中(确保通过调用者调用表单io队列)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
- (void)_storeImageDataToDisk:(nullable NSData *)imageData forKey:(nullable NSString *)key {
//若图片或者key不存在,则不能存储
if (!imageData || !key) {
return;
}
//如果文件管理器中不存在磁盘缓存的路径,则创建
if (![self.fileManager fileExistsAtPath:_diskCachePath]) {
[self.fileManager createDirectoryAtPath:_diskCachePath withIntermediateDirectories:YES attributes:nil error:NULL];
}

//通过key获取缓存路径
NSString *cachePathForKey = [self defaultCachePathForKey:key];
// 转换成 NSUrl
NSURL *fileURL = [NSURL fileURLWithPath:cachePathForKey];

//将图片写入fileURL中(options写入的选项,默认的配置为:NSDataWritingAtomic)
[imageData writeToURL:fileURL options:self.config.diskCacheWritingOptions error:nil];

// 禁用iCloud备份
if (self.config.shouldDisableiCloud) {
//NSURLIsExcludedFromBackupKey:如果应从备份中排除资源,则为true,否则为false(读写,值类型为boolean NSNumber)。 此属性仅用于排除备份中不需要的缓存和其他应用程序支持文件。 通常对用户文档执行的某些操作将导致此属性重置为false,因此不应在用户文档上使用此属性。
[fileURL setResourceValue:@YES forKey:NSURLIsExcludedFromBackupKey error:nil];
}
}

查询和检索操作

异步检查磁盘缓存中是否存在图片(不加载图片),回调返回结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* 异步检查串行队列的磁盘缓存中是否存在图片(不加载图片),回调返回结果
*
* @param key 描述url的key
* @param completionBlock 检查完成时要执行的块。
* @note 将在主队列上始终执行完成块
*/
- (void)diskImageExistsWithKey:(nullable NSString *)key completion:(nullable SDWebImageCheckCacheCompletionBlock)completionBlock {
dispatch_async(self.ioQueue, ^{
// 根据key判断磁盘中是否图片数据
BOOL exists = [self _diskImageDataExistsWithKey:key];
if (completionBlock) {
//如果回调代码存在,主线程执行完成查询的回调
dispatch_async(dispatch_get_main_queue(), ^{
completionBlock(exists);
});
}
});
}

同步检查磁盘缓存中是否存在图片(不加载图片),直接返回结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* 同步检查磁盘缓存中是否存在图片(不加载图片),直接返回结果
*
* @param key 描述url的key
*/
- (BOOL)diskImageDataExistsWithKey:(nullable NSString *)key {
//如果key不存在,返回查询结果为NO ,否则同步根据key同步查询是否存在图片,返回结果
if (!key) {
return NO;
}
__block BOOL exists = NO;
dispatch_sync(self.ioQueue, ^{
exists = [self _diskImageDataExistsWithKey:key];
});

return exists;
}

根据key判断磁盘中是否存在图片数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
- (BOOL)_diskImageDataExistsWithKey:(nullable NSString *)key {
//如果key不存在,返回查询结果为NO
if (!key) {
return NO;
}
//key存在,则判断文件管理器中是否存在该key的缓存路径,如果存在返回,
BOOL exists = [self.fileManager fileExistsAtPath:[self defaultCachePathForKey:key]];

// 如果不存在,进一步判断是否存在该key删除扩展名的缓存路径,返回判断结果
if (!exists) {
//stringByDeletingPathExtension:从文件的最后一部分删除扩展名
exists = [self.fileManager fileExistsAtPath:[self defaultCachePathForKey:key].stringByDeletingPathExtension];
}

return exists;
}

根据key同步查询图片数据data

1
2
3
4
5
6
7
8
9
10
11
12
13
14
- (nullable NSData *)diskImageDataForKey:(nullable NSString *)key {
//如果key不存在,返回nil
if (!key) {
return nil;
}

//根据key搜索所有的路径获取磁盘图片data
__block NSData *imageData = nil;
dispatch_sync(self.ioQueue, ^{
imageData = [self diskImageDataBySearchingAllPathsForKey:key];
});

return imageData;
}

根据key同步查询内存缓存图片

1
2
3
- (nullable UIImage *)imageFromMemoryCacheForKey:(nullable NSString *)key {
return [self.memCache objectForKey:key];
}

根据key同步查询磁盘缓存图片

1
2
3
4
5
6
7
8
9
10
11
12
- (nullable UIImage *)imageFromDiskCacheForKey:(nullable NSString *)key {
//根据key查询磁盘缓存图片,最终调用的是:- (UIImage *)diskImageForKey: data: options:这个方法
UIImage *diskImage = [self diskImageForKey:key];

//如果图片存在,并且需要缓存到内存中,则计算所占用字节数,并缓存到内存中
if (diskImage && self.config.shouldCacheImagesInMemory) {
NSUInteger cost = SDCacheCostForImage(diskImage);
[self.memCache setObject:diskImage forKey:key cost:cost];
}

return diskImage;
}

检查缓存后,同步查询缓存(磁盘或内存)

1
2
3
4
5
6
7
8
9
10
11
- (nullable UIImage *)imageFromCacheForKey:(nullable NSString *)key {
// 先从内存中查询缓存图片,如果存在,结束查询并返回图片
UIImage *image = [self imageFromMemoryCacheForKey:key];
if (image) {
return image;
}

//如果内存中未查到该key的图片,则从磁盘中查询,返回最后查询的结果
image = [self imageFromDiskCacheForKey:key];
return image;
}

根据key搜索所有的路径获取磁盘图片data

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
- (nullable NSData *)diskImageDataBySearchingAllPathsForKey:(nullable NSString *)key {
//根据key获取默认的缓存路径
NSString *defaultPath = [self defaultCachePathForKey:key];
//self.config.diskCacheReadingOptions :默认是0,即:NSDataReadingMappedIfSafe
//根据缓存路径和磁盘缓存读取选项,获取图片data,若存在则返回data,不存在则继续读取该key删除扩展名的缓存路径
NSData *data = [NSData dataWithContentsOfFile:defaultPath options:self.config.diskCacheReadingOptions error:nil];
if (data) {
return data;
}

//读取该key删除扩展名的缓存路径,获取图片data,若存在则返回data
data = [NSData dataWithContentsOfFile:defaultPath.stringByDeletingPathExtension options:self.config.diskCacheReadingOptions error:nil];
if (data) {
return data;
}
如果上面均未读取tupiandata,则依据上面的方法查找自定义路径,若存在返回data,如果仍未找到则返回nil
NSArray<NSString *> *customPaths = [self.customPaths copy];
for (NSString *path in customPaths) {
NSString *filePath = [self cachePathForKey:key inPath:path];
NSData *imageData = [NSData dataWithContentsOfFile:filePath options:self.config.diskCacheReadingOptions error:nil];
if (imageData) {
return imageData;
}

imageData = [NSData dataWithContentsOfFile:filePath.stringByDeletingPathExtension options:self.config.diskCacheReadingOptions error:nil];
if (imageData) {
return imageData;
}
}

return nil;
}

根据key获取磁盘图片

1
2
3
4
5
6
缓存图片到磁盘是存储的imageData;到内存是存储的image。
- (nullable UIImage *)diskImageForKey:(nullable NSString *)key {
//现获取imagedata,再转换成image
NSData *data = [self diskImageDataForKey:key];
return [self diskImageForKey:key data:data];
}

根据key和data得到磁盘图片

1
2
3
- (nullable UIImage *)diskImageForKey:(nullable NSString *)key data:(nullable NSData *)data {
return [self diskImageForKey:key data:data options:0];
}

//最终调用的方法,根据key和data、选项获取磁盘图片

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
- (nullable UIImage *)diskImageForKey:(nullable NSString *)key data:(nullable NSData *)data options:(SDImageCacheOptions)options {
//如果图片的data存在,进行进一步的转换,否则返回nil
if (data) {
//将data转换成image
UIImage *image = [[SDWebImageCodersManager sharedInstance] decodedImageWithData:data];
//对图片进行缩放操作
image = [self scaledImageForKey:key image:image];
//如果需要解压缩操作,就进行其操作,否则直接返回图片
if (self.config.shouldDecompressImages) {
//默认情况下,图像会根据其原始大小进行解码。在iOS上,此选项会将图像缩小到与设备的受限内存兼容的大小。
BOOL shouldScaleDown = options & SDImageCacheScaleDownLargeImages;
/**
*- (nullable UIImage *)decompressedImageWithImage:(nullable UIImage *)image
data:(NSData * _Nullable * _Nonnull)data
options:(nullable NSDictionary<NSString*, NSObject*>*)optionsDict;
* 使用原始图像和图像数据解压缩图像。
*
* @param image要解压缩的原始图像
* @param data指向原始图像数据的指针。 指针本身是非空的,但图像数据可以为空。 如果需要,此数据将设置为缓存。 如果您不需要同时修改数据,请忽略此参数。
* @param optionsDict包含任何解压缩选项的字典。 通过{SDWebImageCoderScaleDownLargeImagesKey:@(YES)}缩小大图像
* @return解压缩的图像
*/

image = [[SDWebImageCodersManager sharedInstance] decompressedImageWithImage:image data:&data options:@{SDWebImageCoderScaleDownLargeImagesKey: @(shouldScaleDown)}];
}
return image;
} else {
return nil;
}
}

//图片缩放操作

1
2
3
- (nullable UIImage *)scaledImageForKey:(nullable NSString *)key image:(nullable UIImage *)image {
return SDScaledImageForKey(key, image);
}

异步查询缓存并在完成后调用完成的操作

1
2
3
4
5
6
7
8
9
10
11
/**
* 异步查询缓存并在完成后调用完成的操作。
*
* @param key 用来存储所需图片唯一的key
* @param doneBlock The completion block. 如果操作被取消,则不会被调用
*
* @return 包含缓存操作的NSOperation实例
*/
- (NSOperation *)queryCacheOperationForKey:(NSString *)key done:(SDCacheQueryCompletedBlock)doneBlock {
return [self queryCacheOperationForKey:key options:0 done:doneBlock];
}

异步查询缓存并在完成后调用完成的操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
/**
* 异步查询缓存并在完成后调用完成的操作。
*
* @param key 用来存储所需图片唯一的key
* @param options 用于指定用于此高速缓存查询的选项
* @param doneBlock The completion block. 如果操作被取消,则不会被调用
*
* @return 包含缓存操作的NSOperation实例
*/
- (nullable NSOperation *)queryCacheOperationForKey:(nullable NSString *)key options:(SDImageCacheOptions)options done:(nullable SDCacheQueryCompletedBlock)doneBlock {
//如果key不存在,返回查询操作为nil,如果执行回调,则image,data传nil,类型传SDImageCacheTypeNone
if (!key) {
if (doneBlock) {
doneBlock(nil, nil, SDImageCacheTypeNone);
}
return nil;
}

// 先从内存中查找图片,
UIImage *image = [self imageFromMemoryCacheForKey:key];
BOOL shouldQueryMemoryOnly = (image && !(options & SDImageCacheQueryDataWhenInMemory));
如果image存在,并且只从内存中查找,返回NSOperation为nil,,如果执行回调,则传image为查找的image,data传nil,类型传SDImageCacheTypeMemory
if (shouldQueryMemoryOnly) {
if (doneBlock) {
doneBlock(image, nil, SDImageCacheTypeMemory);
}
return nil;
}

//创建一个NSOperation来获取磁盘图片
NSOperation *operation = [NSOperation new];
void(^queryDiskBlock)(void) = ^{
if (operation.isCancelled) {
// 如果操作被取消,则不执行回调
return;
}

//在自动释放池中执行,当@autoreleasepool结束时,里面的内存就会回收
@autoreleasepool {
//获取缓存data
NSData *diskData = [self diskImageDataBySearchingAllPathsForKey:key];
UIImage *diskImage;
SDImageCacheType cacheType = SDImageCacheTypeDisk;
if (image) {
// 如果从内存中查找的image存在,赋值给diskImage,缓存类型为SDImageCacheTypeMemory
diskImage = image;
cacheType = SDImageCacheTypeMemory;
} else if (diskData) {
// 如果内存缓存未找到image,并且缓存data存在,通过diskData转换为image
diskImage = [self diskImageForKey:key data:diskData options:options];
if (diskImage && self.config.shouldCacheImagesInMemory) {
//磁盘图片存在,并且需要缓存到内存,则做内存存储图片操作
NSUInteger cost = SDCacheCostForImage(diskImage);
[self.memCache setObject:diskImage forKey:key cost:cost];
}
}
//完成回调存在,如果选项为:SDImageCacheQueryDiskSync(此选项可以强制同步查询磁盘缓存),则执行同步回调,否则在主线程执行回调
if (doneBlock) {
if (options & SDImageCacheQueryDiskSync) {
doneBlock(diskImage, diskData, cacheType);
} else {
dispatch_async(dispatch_get_main_queue(), ^{
doneBlock(diskImage, diskData, cacheType);
});
}
}
}
};

//如果选项为:SDImageCacheQueryDiskSync(此选项可以强制同步查询磁盘缓存),则执行同步执行上面的queryDiskBlock代码块,否则异步执行
if (options & SDImageCacheQueryDiskSync) {
queryDiskBlock();
} else {
dispatch_async(self.ioQueue, queryDiskBlock);
}

return operation;
}

移除操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
/**
* 从内存或者磁盘缓存中异步移除图片
*
* @param key 唯一的图片缓存key
* @param completion 删除图像后应执行的块(可选)
*/

- (void)removeImageForKey:(nullable NSString *)key withCompletion:(nullable SDWebImageNoParamsBlock)completion {
[self removeImageForKey:key fromDisk:YES withCompletion:completion];
}

/**
* 从内存和可选磁盘缓存中异步移除图片
*
* @param key 唯一的图片缓存key
* @param fromDisk 是否也从磁盘中移除
* @param completion 删除图像后应执行的块(可选)
*/
- (void)removeImageForKey:(nullable NSString *)key fromDisk:(BOOL)fromDisk withCompletion:(nullable SDWebImageNoParamsBlock)completion {
//key为空,返回
if (key == nil) {
return;
}

//如果缓存配置允许缓存到内存上,则需要在内存上也删除该key的缓存
if (self.config.shouldCacheImagesInMemory) {
[self.memCache removeObjectForKey:key];
}
//如果需要从磁盘上移除缓存,则执行磁盘移除缓存操作
if (fromDisk) {
//异步执行移除磁盘缓存操作
dispatch_async(self.ioQueue, ^{
[self.fileManager removeItemAtPath:[self defaultCachePathForKey:key] error:nil];

//主线程执行完成回调
if (completion) {
dispatch_async(dispatch_get_main_queue(), ^{
completion();
});
}
});
} else if (completion){
//执行完成回调
completion();
}

}

缓存清理操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
//清理缓存
- (void)clearMemory {
//清理所有内存缓存
[self.memCache removeAllObjects];
}

//异步清理磁盘缓存,回调结果
- (void)clearDiskOnCompletion:(nullable SDWebImageNoParamsBlock)completion {
dispatch_async(self.ioQueue, ^{
//删除该磁盘缓存路径,之后再重新创建一个作为新的缓存路径(其实就是同一个路径,目的就是删除缓存数据)
[self.fileManager removeItemAtPath:self.diskCachePath error:nil];
[self.fileManager createDirectoryAtPath:self.diskCachePath
withIntermediateDirectories:YES
attributes:nil
error:NULL];

if (completion) {
//主线程执行完成回调
dispatch_async(dispatch_get_main_queue(), ^{
completion();
});
}
});
}

//删除过期的文件
- (void)deleteOldFiles {
[self deleteOldFilesWithCompletionBlock:nil];
}

//异步从磁盘中删除所有过期的缓存图片
- (void)deleteOldFilesWithCompletionBlock:(nullable SDWebImageNoParamsBlock)completionBlock {
dispatch_async(self.ioQueue, ^{
//获取磁盘缓存的默认根目录
NSURL *diskCacheURL = [NSURL fileURLWithPath:self.diskCachePath isDirectory:YES];

// 计算用于测试的内容日期key
NSURLResourceKey cacheContentDateKey = NSURLContentModificationDateKey;
switch (self.config.diskCacheExpireType) {
case SDImageCacheConfigExpireTypeAccessDate:
cacheContentDateKey = NSURLContentAccessDateKey;
break;

case SDImageCacheConfigExpireTypeModificationDate:
cacheContentDateKey = NSURLContentModificationDateKey;
break;

default:
break;
}

//记录遍历需要预先获取文件的哪些属性
NSArray<NSString *> *resourceKeys = @[NSURLIsDirectoryKey, cacheContentDateKey, NSURLTotalFileAllocatedSizeKey];

// diskCacheURL 和 resourceKeys 这两个变量主要是为了下面生成NSDirectoryEnumerator准备的
//此枚举器为我们的缓存文件预取有用的属性。
/**
* 递归地遍历diskCachePath这个文件夹中的所有目录,此处不是直接使用diskCachePath,而是使用其生成的NSURL
* 此处使用includingPropertiesForKeys:resourceKeys,这样每个file的resourceKeys对应的属性也会在遍历时预先获取到
* NSDirectoryEnumerationSkipsHiddenFiles表示不遍历隐藏文件
*/

NSDirectoryEnumerator *fileEnumerator = [self.fileManager enumeratorAtURL:diskCacheURL
includingPropertiesForKeys:resourceKeys
options:NSDirectoryEnumerationSkipsHiddenFiles
errorHandler:NULL];

/**
* 获取文件的过期时间,SDWebImage中默认是一个星期
* expirationDate为过期时间,例如:现在时间是2018/10/16/00:00:00,当前时间减去1个星期,得到
* 2018/10/09/00:00:00,这个时间为函数中的expirationDate
* 用这个expirationDate和最后一次修改时间modificationDate比较看谁更晚就行
*/
NSDate *expirationDate = [NSDate dateWithTimeIntervalSinceNow:-self.config.maxCacheAge];
//用来存储对应文件的一些属性,比如文件所需磁盘空间
NSMutableDictionary<NSURL *, NSDictionary<NSString *, id> *> *cacheFiles = [NSMutableDictionary dictionary];
//记录党建已经使用的磁盘缓存大小
NSUInteger currentCacheSize = 0;

// 在缓存的目录开始遍历文件. 此次遍历有两个目的:
// 1. 移除过期的文件
// 2. 同时存储每个文件的属性(比如该file是否是文件夹、该file所需磁盘大小,修改时间)
NSMutableArray<NSURL *> *urlsToDelete = [[NSMutableArray alloc] init];
for (NSURL *fileURL in fileEnumerator) {
NSError *error;
NSDictionary<NSString *, id> *resourceValues = [fileURL resourceValuesForKeys:resourceKeys error:&error];

// 当前扫描的是目录,就跳过
if (error || !resourceValues || [resourceValues[NSURLIsDirectoryKey] boolValue]) {
continue;
}

// 移除过期文件(这里判断过期的方式:对比文件的最后一次修改日期和expirationDate谁更晚,如果expirationDate更晚,就认为该文件已经过期)
NSDate *modifiedDate = resourceValues[cacheContentDateKey];
if ([[modifiedDate laterDate:expirationDate] isEqualToDate:expirationDate]) {
[urlsToDelete addObject:fileURL];
continue;
}

// 计算当前已经使用的cache大小,并将对应file的属性存到cacheFiles中
NSNumber *totalAllocatedSize = resourceValues[NSURLTotalFileAllocatedSizeKey];
currentCacheSize += totalAllocatedSize.unsignedIntegerValue;
cacheFiles[fileURL] = resourceValues;
}

// 根据需要移除文件的url来移除对应file
for (NSURL *fileURL in urlsToDelete) {
[self.fileManager removeItemAtURL:fileURL error:nil];
}

// 如果我们当前cache的大小已经超过了允许配置的缓存大小,那就删除已经缓存的文件
// 删除策略就是,首先删除修改时间更早的缓存文件
if (self.config.maxCacheSize > 0 && currentCacheSize > self.config.maxCacheSize) {
// 直接将当前cache大小降到允许最大的cache大小的一般
//预期的缓存大小
const NSUInteger desiredCacheSize = self.config.maxCacheSize / 2;

// 根据文件修改时间来给所有缓存文件排序,按照修改时间越早越在前的规则排序
NSArray<NSURL *> *sortedFiles = [cacheFiles keysSortedByValueWithOptions:NSSortConcurrent
usingComparator:^NSComparisonResult(id obj1, id obj2) {
return [obj1[NSURLContentModificationDateKey] compare:obj2[NSURLContentModificationDateKey]];
}];

// 每次删除file后,就计算此时的cache的大小.
//如果此时的cache大小已经降到期望的大小了,就停止删除文件了
for (NSURL *fileURL in sortedFiles) {
if ([self.fileManager removeItemAtURL:fileURL error:nil]) {
// 获取该文件对应的属性
NSDictionary<NSString *, id> *resourceValues = cacheFiles[fileURL];
// 根据resourceValues获取该文件所需磁盘空间大小
NSNumber *totalAllocatedSize = resourceValues[NSURLTotalFileAllocatedSizeKey];
// 计算当前cache大小
currentCacheSize -= totalAllocatedSize.unsignedIntegerValue;

if (currentCacheSize < desiredCacheSize) {
如果当前的缓存小于预期的缓存,结束删除file操作
break;
}
}
}
}

// 如果有completionBlock,就在主线程中调用
if (completionBlock) {
dispatch_async(dispatch_get_main_queue(), ^{
completionBlock();
});
}
});
}

#if SD_UIKIT
//后台删除过期文件
- (void)backgroundDeleteOldFiles {
Class UIApplicationClass = NSClassFromString(@"UIApplication");
if(!UIApplicationClass || ![UIApplicationClass respondsToSelector:@selector(sharedApplication)]) {
return;
}
UIApplication *application = [UIApplication performSelector:@selector(sharedApplication)];
//如果backgroundTask对应的时间结束了,任务还没有处理完成,则直接终止任务
__block UIBackgroundTaskIdentifier bgTask = [application beginBackgroundTaskWithExpirationHandler:^{
//通过标记您的位置来清理任何未完成的任务业务
//完全停止或结束任务。
//当任务非正常终止的时候,做清理工作
[application endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}];

// 启动长时间运行的任务并立即返回。
//图片清理结束以后,处理完成
[self deleteOldFilesWithCompletionBlock:^{
//清理完成以后,终止任务
[application endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}];
}
#endif

缓存信息

获取磁盘缓存使用的大小

1
2
3
4
5
6
7
8
9
10
11
12
13
- (NSUInteger)getSize {
__block NSUInteger size = 0;
// 需要同步操作:等待队列self.ioQueue中的任务执行完后(有可能队列中的任务正在添加图片或者删除图片操作),再进行获取文件大小计算
dispatch_sync(self.ioQueue, ^{
NSDirectoryEnumerator *fileEnumerator = [self.fileManager enumeratorAtPath:self.diskCachePath];
for (NSString *fileName in fileEnumerator) {
NSString *filePath = [self.diskCachePath stringByAppendingPathComponent:fileName];
NSDictionary<NSString *, id> *attrs = [self.fileManager attributesOfItemAtPath:filePath error:nil];
size += [attrs fileSize];
}
});
return size;
}

获取磁盘缓存中的图片数量

1
2
3
4
5
6
7
8
- (NSUInteger)getDiskCount {
__block NSUInteger count = 0;
dispatch_sync(self.ioQueue, ^{
NSDirectoryEnumerator *fileEnumerator = [self.fileManager enumeratorAtPath:self.diskCachePath];
count = fileEnumerator.allObjects.count;
});
return count;
}

异步计算磁盘缓存的大小

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
- (void)calculateSizeWithCompletionBlock:(nullable SDWebImageCalculateSizeBlock)completionBlock {
NSURL *diskCacheURL = [NSURL fileURLWithPath:self.diskCachePath isDirectory:YES];

dispatch_async(self.ioQueue, ^{
NSUInteger fileCount = 0;
NSUInteger totalSize = 0;

NSDirectoryEnumerator *fileEnumerator = [self.fileManager enumeratorAtURL:diskCacheURL
includingPropertiesForKeys:@[NSFileSize]
options:NSDirectoryEnumerationSkipsHiddenFiles
errorHandler:NULL];

for (NSURL *fileURL in fileEnumerator) {
NSNumber *fileSize;
[fileURL getResourceValue:&fileSize forKey:NSURLFileSizeKey error:NULL];
totalSize += fileSize.unsignedIntegerValue;
fileCount += 1;
}

if (completionBlock) {
dispatch_async(dispatch_get_main_queue(), ^{
completionBlock(fileCount, totalSize);
});
}
});
}

属性Get和Set方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//缓存中最大的消耗的内存,这里计算的是内存中的像素个数
- (NSUInteger)maxMemoryCost {
return self.memCache.totalCostLimit;
}
//设置缓存中最大的消耗的内存,这里计算的是内存中的像素个数
- (void)setMaxMemoryCost:(NSUInteger)maxMemoryCost {
self.memCache.totalCostLimit = maxMemoryCost;
}

//缓存应持有的对象的的最大数量
- (NSUInteger)maxMemoryCountLimit {
return self.memCache.countLimit;
}

//设置缓存应持有的对象的的最大数量
- (void)setMaxMemoryCountLimit:(NSUInteger)maxCountLimit {
self.memCache.countLimit = maxCountLimit;
}