SDWebImage源码解析(三)

Posted by 黄成都 on 2017-05-03
Words 7.4k and Reading Time 34 Minutes
Viewed Times

1 概述

这篇博文中,我将分析SDWebImageManagerSDImageCacheSDWebImageManager拥有一个SDWebImageCacheSDWebImageDownloader属性分别用于图片的缓存和加载处理。为UIView及其子类提供了加载图片的统一接口。管理正在加载操作的集合,这个类是一个单列。同时管理各种加载选项的处理。SDImageCache负责SDWebImage的整个缓存工作,是一个单列对象。缓存路径处理、缓存名字处理、管理内存缓存和磁盘缓存的创建和删除、根据指定key获取图片、存入图片的类型处理、根据缓存的创建和修改日期删除缓存。

2 SDWebImageManager分析

UIImageView等各种视图通过UIView+WebCache分类的sd_internalSetImageWithURL方法来调用SDWebImageManager类的如下方法实现图片加载:

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
这个方法是核心方法。UIImageView等这种分类都默认通过调用这个方法来获取数据。

@param url 图片的url地址
@param options 获取图片的属性
@param progressBlock 加载进度回调
@param completedBlock 加载完成回调
@return 返回一个加载的载体对象。以便提供给后面取消删除等。
*/
- (nullable id <SDWebImageOperation>)loadImageWithURL:(nullable NSURL *)url
options:(SDWebImageOptions)options
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDInternalCompletionBlock)completedBlock;

首先看他地第二个参数options。这个参数指定了图片加载过程中的不同选项。指定不同选项,SDWebImage可以根据选项做不同的处理,这是一个枚举类型,多个选项之间可以组合使用。

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
/**
枚举,定义了图片加载处理过程中的选项
*/
typedef NS_OPTIONS(NSUInteger, SDWebImageOptions) {
/*
默认情况下,当一个URL下载失败的时候,这个URL会被加入黑名单列表,下次再有这个url的请求则停止请求。
如果为true,这个值表示需要再尝试请求。
*/
SDWebImageRetryFailed = 1 << 0,

/*
默认情况下,当UI可以交互的时候就开始加载图片。这个标记可以阻止这个时候加载。
而是当UIScrollView开始减速滑动的时候开始加载。
*/
SDWebImageLowPriority = 1 << 1,

/*
这个属性禁止磁盘缓存
*/
SDWebImageCacheMemoryOnly = 1 << 2,

/*
这个标记允许图片在加载过程中显示,就像浏览器那样。
默认情况下,图片只会在加载完成以后再显示。
*/
SDWebImageProgressiveDownload = 1 << 3,

/*
*即使本地已经缓存了图片,但是根据HTTP的缓存策略去网络上加载图片。也就是说本地缓存了也不管了,尝试从网络上加载数据。但是具体是从代理加载、HTTP缓存加载、还是原始服务器加载这个就更具HTTP的请求头配置。
*使用NSURLCache而不是SDWebImage来处理磁盘缓存。从而可能会导致轻微的性能损害。
*这个选项专门用于处理,url地址没有变,但是url对于的图片数据在服务器改变的情况。
*如果一个缓存图片更新了,则completion这个回调会被调用两次,一次返回缓存图片,一次返回最终图片。
*我们只有在不能确保URL和他对应的内容不能完全对应的时候才使用这个标记。
*/
SDWebImageRefreshCached = 1 << 4,

/*
当应用进入后台以后,图片继续下载。应用进入后台以后,通过向系统申请额外的时间来完成。如果时间超时,那么下载操作会被取消。
*/
SDWebImageContinueInBackground = 1 << 5,

/*
处理缓存在`NSHTTPCookieStore`对象里面的cookie。通过设置`NSMutableURLRequest.HTTPShouldHandleCookies = YES`来实现的。
*/
SDWebImageHandleCookies = 1 << 6,

/*
*允许非信任的SSL证书请求。
*在测试的时候很有用。但是正式环境要小心使用。
*/
SDWebImageAllowInvalidSSLCertificates = 1 << 7,

/*
* 默认情况下,图片加载的顺序是根据加入队列的顺序加载的。但是这个标记会把任务加入队列的最前面。
*/
SDWebImageHighPriority = 1 << 8,

/*
默认情况下,在图片加载的过程中,会显示占位图。
但是这个标记会阻止显示占位图直到图片加载完成。
*/
SDWebImageDelayPlaceholder = 1 << 9,

/*
*默认情况下,我们不会去调用`animated images`(估计就是多张图片循环显示或者GIF图片)的`transformDownloadedImage`代理方法来处理图片。因为大部分transformation操作会对图片做无用处理。
*用这个标记表示无论如何都要对图片做transform处理。
*/
SDWebImageTransformAnimatedImage = 1 << 10,

/*
*默认情况下,图片再下载完成以后都会被自动加载到UIImageView对象上面。但是有时我们希望UIImageView加载我们手动处理以后的图片。
*这个标记允许我们在completion这个Block中手动设置处理好以后的图片。
*/
SDWebImageAvoidAutoSetImage = 1 << 11,

/*
*默认情况下,图片会按照他的原始大小来解码显示。根据设备的内存限制,这个属性会调整图片的尺寸到合适的大小再解压缩。
*如果`SDWebImageProgressiveDownload`标记被设置了,则这个flag不起作用。
*/
SDWebImageScaleDownLargeImages = 1 << 12
};

接下来我先看SDWebImageManager的初始化过程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
- (nonnull instancetype)init {
SDImageCache *cache = [SDImageCache sharedImageCache];
SDWebImageDownloader *downloader = [SDWebImageDownloader sharedDownloader];
return [self initWithCache:cache downloader:downloader];
}

/**
初始化SDImageCache和SDWebImageDownloader对象

@param cache SDImageCache对象
@param downloader SDWebImageDownloader对象
@return 返回初始化结果
*/
- (nonnull instancetype)initWithCache:(nonnull SDImageCache *)cache downloader:(nonnull SDWebImageDownloader *)downloader {
if ((self = [super init])) {
_imageCache = cache;
_imageDownloader = downloader;
//用于保存加载失败的url集合
_failedURLs = [NSMutableSet new];
//用于保存当前正在加载的Operation
_runningOperations = [NSMutableArray new];
}
return self;
}

loadImageWithURL方法是SDWebImageManager最核心的方法,实现过程:

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
178
179
180
181
182
183
184
/**
这个方法是核心方法。UIImageView等这种分类都默认通过调用这个方法来获取数据。

@param url 图片的url地址
@param options 获取图片的属性
@param progressBlock 加载进度回调
@param completedBlock 加载完成回调
@return 返回一个加载的载体对象。以便提供给后面取消删除等。
*/
- (id <SDWebImageOperation>)loadImageWithURL:(nullable NSURL *)url
options:(SDWebImageOptions)options
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDInternalCompletionBlock)completedBlock {
/*
如果传入的url是NSString格式的。则转换为NSURL类型再处理
*/
if ([url isKindOfClass:NSString.class]) {
url = [NSURL URLWithString:(NSString *)url];
}

// Prevents app crashing on argument type error like sending NSNull instead of NSURL
//如果url不会NSURL类型的对象。则置为nil
if (![url isKindOfClass:NSURL.class]) {
url = nil;
}
/*
图片加载获取获取过程中绑定一个`SDWebImageCombinedOperation`对象。以方便后续再通过这个对象对url的加载控制。
*/
__block SDWebImageCombinedOperation *operation = [SDWebImageCombinedOperation new];
__weak SDWebImageCombinedOperation *weakOperation = operation;

BOOL isFailedUrl = NO;
//当前url是否在失败url的集合里面
if (url) {
@synchronized (self.failedURLs) {
isFailedUrl = [self.failedURLs containsObject:url];
}
}
/*
如果url是失败的url或者url有问题等各种问题。则直接根据opeation来做异常情况的处理
*/
if (url.absoluteString.length == 0 || (!(options & SDWebImageRetryFailed) && isFailedUrl)) {
//构建回调Block
[self callCompletionBlockForOperation:operation completion:completedBlock error:[NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorFileDoesNotExist userInfo:nil] url:url];
return operation;
}
//把加载图片的一个载体存入runningOperations。里面是所有正在做图片加载过程的operation的集合。
@synchronized (self.runningOperations) {
[self.runningOperations addObject:operation];
}
//根据url获取url对应的key
NSString *key = [self cacheKeyForURL:url];
/*
*如果图片是从内存加载,则返回的cacheOperation是nil,
*如果是从磁盘加载,则返回的cacheOperation是`NSOperation`对象。
*如果是从网络加载,则返回的cacheOperation对象是`SDWebImageDownloaderOperation`对象。
*/
operation.cacheOperation = [self.imageCache queryCacheOperationForKey:key done:^(UIImage *cachedImage, NSData *cachedData, SDImageCacheType cacheType) {
//从缓存中获取图片数据返回
//如果已经取消了操作。则直接返回并且移除对应的opetation对象
if (operation.isCancelled) {
[self safelyRemoveOperationFromRunning:operation];
return;
}

if ((!cachedImage || options & SDWebImageRefreshCached) && (![self.delegate respondsToSelector:@selector(imageManager:shouldDownloadImageForURL:)] || [self.delegate imageManager:self shouldDownloadImageForURL:url])) {
/**
如果从缓存获取图片失败。或者设置了SDWebImageRefreshCached来忽略缓存。则先把缓存的图片返回。
*/
if (cachedImage && options & SDWebImageRefreshCached) {
//构建回调Block
[self callCompletionBlockForOperation:weakOperation completion:completedBlock image:cachedImage data:cachedData error:nil cacheType:cacheType finished:YES url:url];
}

// download if no image or requested to refresh anyway, and download allowed by delegate
/*
把图片加载的`SDWebImageOptions`类型枚举转换为图片下载的`SDWebImageDownloaderOptions`类型的枚举
*/
SDWebImageDownloaderOptions downloaderOptions = 0;
if (options & SDWebImageLowPriority) downloaderOptions |= SDWebImageDownloaderLowPriority;
if (options & SDWebImageProgressiveDownload) downloaderOptions |= SDWebImageDownloaderProgressiveDownload;
if (options & SDWebImageRefreshCached) downloaderOptions |= SDWebImageDownloaderUseNSURLCache;
if (options & SDWebImageContinueInBackground) downloaderOptions |= SDWebImageDownloaderContinueInBackground;
if (options & SDWebImageHandleCookies) downloaderOptions |= SDWebImageDownloaderHandleCookies;
if (options & SDWebImageAllowInvalidSSLCertificates) downloaderOptions |= SDWebImageDownloaderAllowInvalidSSLCertificates;
if (options & SDWebImageHighPriority) downloaderOptions |= SDWebImageDownloaderHighPriority;
if (options & SDWebImageScaleDownLargeImages) downloaderOptions |= SDWebImageDownloaderScaleDownLargeImages;
/*
如果设置了强制刷新缓存的选项。则`SDWebImageDownloaderProgressiveDownload`选项失效并且添加`SDWebImageDownloaderIgnoreCachedResponse`选项。
*/
if (cachedImage && options & SDWebImageRefreshCached) {
// force progressive off if image already cached but forced refreshing
downloaderOptions &= ~SDWebImageDownloaderProgressiveDownload;
// ignore image read from NSURLCache if image if cached but force refreshing
downloaderOptions |= SDWebImageDownloaderIgnoreCachedResponse;
}
/*
新建一个网络下载的操作。
*/
SDWebImageDownloadToken *subOperationToken = [self.imageDownloader downloadImageWithURL:url options:downloaderOptions progress:progressBlock completed:^(UIImage *downloadedImage, NSData *downloadedData, NSError *error, BOOL finished) {
__strong __typeof(weakOperation) strongOperation = weakOperation;
//如果图片下载结束以后,对应的图片加载操作已经取消。则什么处理都不做
if (!strongOperation || strongOperation.isCancelled) {
//如果operation已经被取消了,则什么也不做
} else if (error) {
//如果加载出错。则直接返回回调。并且添加到failedURLs中
[self callCompletionBlockForOperation:strongOperation completion:completedBlock error:error url:url];

if ( error.code != NSURLErrorNotConnectedToInternet
&& error.code != NSURLErrorCancelled
&& error.code != NSURLErrorTimedOut
&& error.code != NSURLErrorInternationalRoamingOff
&& error.code != NSURLErrorDataNotAllowed
&& error.code != NSURLErrorCannotFindHost
&& error.code != NSURLErrorCannotConnectToHost
&& error.code != NSURLErrorNetworkConnectionLost) {
@synchronized (self.failedURLs) {
[self.failedURLs addObject:url];
}
}
}
else {
//网络图片加载成功
if ((options & SDWebImageRetryFailed)) {
//如果有重试失败下载的选项。则把url从failedURLS中移除
@synchronized (self.failedURLs) {
[self.failedURLs removeObject:url];
}
}

BOOL cacheOnDisk = !(options & SDWebImageCacheMemoryOnly);

if (options & SDWebImageRefreshCached && cachedImage && !downloadedImage) {
// Image refresh hit the NSURLCache cache, do not call the completion block
//如果成功下载图片。并且图片是动态图片。并且设置了SDWebImageTransformAnimatedImage属性。则处理图片
} else if (downloadedImage && (!downloadedImage.images || (options & SDWebImageTransformAnimatedImage)) && [self.delegate respondsToSelector:@selector(imageManager:transformDownloadedImage:withURL:)]) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
//获取transform以后的图片
UIImage *transformedImage = [self.delegate imageManager:self transformDownloadedImage:downloadedImage withURL:url];
//存储transform以后的的图片
if (transformedImage && finished) {
BOOL imageWasTransformed = ![transformedImage isEqual:downloadedImage];
// pass nil if the image was transformed, so we can recalculate the data from the image
[self.imageCache storeImage:transformedImage imageData:(imageWasTransformed ? nil : downloadedData) forKey:key toDisk:cacheOnDisk completion:nil];
}
//回调拼接
[self callCompletionBlockForOperation:strongOperation completion:completedBlock image:transformedImage data:downloadedData error:nil cacheType:SDImageCacheTypeNone finished:finished url:url];
});
} else {
//如果成功下载图片。并且图片不是图片。则直接缓存和回调
if (downloadedImage && finished) {
[self.imageCache storeImage:downloadedImage imageData:downloadedData forKey:key toDisk:cacheOnDisk completion:nil];
}
//回调拼接
[self callCompletionBlockForOperation:strongOperation completion:completedBlock image:downloadedImage data:downloadedData error:nil cacheType:SDImageCacheTypeNone finished:finished url:url];
}
}
//从正在加载的图片操作集合中移除当前操作
if (finished) {
[self safelyRemoveOperationFromRunning:strongOperation];
}
}];
//重置cancelBlock,取消下载operation
operation.cancelBlock = ^{
[self.imageDownloader cancel:subOperationToken];
__strong __typeof(weakOperation) strongOperation = weakOperation;
[self safelyRemoveOperationFromRunning:strongOperation];
};
} else if (cachedImage) {
//如果获取到了缓存图片。在直接通过缓存图片处理
__strong __typeof(weakOperation) strongOperation = weakOperation;
[self callCompletionBlockForOperation:strongOperation completion:completedBlock image:cachedImage data:cachedData error:nil cacheType:cacheType finished:YES url:url];
[self safelyRemoveOperationFromRunning:operation];
} else {
// Image not in cache and download disallowed by delegate
//图片么有缓存、并且图片也没有下载
__strong __typeof(weakOperation) strongOperation = weakOperation;
[self callCompletionBlockForOperation:strongOperation completion:completedBlock image:nil data:nil error:nil cacheType:SDImageCacheTypeNone finished:YES url:url];
[self safelyRemoveOperationFromRunning:operation];
}
}];

return operation;
}

通过对这个方法的分析,他主要实现的功能有:

  • 创建一个SDWebImageCombinedOperation对象,调用者可以通过这个对象来对加载做取消等操作。这个对象的cancelOperation属性有如下几种情况。
    • 如果图片是从内存加载,则返回的cacheOperation是nil。
    • 如果是从磁盘加载,则返回的cacheOperation是NSOperation对象。
    • 如果是从网络加载,则返回的cacheOperation对象是SDWebImageDownloaderOperation对象。
  • 通过failedURLs属性来保存加载失败的url。通过它可以阻止失败的url再次加载,提高用户体验。
  • 通过runningOperations属性记录当前正在加载的Operation列表。
  • 加载结束以后,通过callCompletionBlockForOperation方法来拼接回调Block。
  • SDWebImageOptions类型的枚举值转换为SDWebImageDownloaderOptions类型的枚举值。
  • 图片成功从网络加载以后,通过imageCache属性的storeImage方法来缓存图片。

另外还有一些辅助性的方法,用于处理缓存判断、url与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
/**
根据url获取url对应的缓存key。
如果有实现指定的url转换key的Block,则用这个方式转换为key。
否则直接用url的绝对值多为key

@param url url
@return 缓存的key
*/
- (nullable NSString *)cacheKeyForURL:(nullable NSURL *)url {
if (!url) {
return @"";
}
if (self.cacheKeyFilter) {
return self.cacheKeyFilter(url);
} else {
return url.absoluteString;
}
}

/**
一个url的缓存是否存在

@param url 缓存数据对应的url
@param completionBlock 缓存结果回调
*/
- (void)cachedImageExistsForURL:(nullable NSURL *)url
completion:(nullable SDWebImageCheckCacheCompletionBlock)completionBlock {
NSString *key = [self cacheKeyForURL:url];
//内存里面是否有key的缓存
BOOL isInMemoryCache = ([self.imageCache imageFromMemoryCacheForKey:key] != nil);
//内存缓存
if (isInMemoryCache) {
// making sure we call the completion block on the main queue
dispatch_async(dispatch_get_main_queue(), ^{
if (completionBlock) {
completionBlock(YES);
}
});
return;
}
//磁盘缓存
[self.imageCache diskImageExistsWithKey:key completion:^(BOOL isInDiskCache) {
// the completion block of checkDiskCacheForImageWithKey:completion: is always called on the main queue, no need to further dispatch
if (completionBlock) {
completionBlock(isInDiskCache);
}
}];
}

/**
url是否有磁盘缓存数据

@param url url
@param completionBlock 回调
*/
- (void)diskImageExistsForURL:(nullable NSURL *)url
completion:(nullable SDWebImageCheckCacheCompletionBlock)completionBlock {
NSString *key = [self cacheKeyForURL:url];

[self.imageCache diskImageExistsWithKey:key completion:^(BOOL isInDiskCache) {
// the completion block of checkDiskCacheForImageWithKey:completion: is always called on the main queue, no need to further dispatch
if (completionBlock) {
completionBlock(isInDiskCache);
}
}];
}

2.1 SDWebImageCombinedOperation分析

从上面的方法,发现loadImageWithURL方法会返回一个SDWebImageCombinedOperation给调用者,这个对象有个cancel方法,这个方法继承自SDWebImageOperation协议。方法里面会调用SDWebImageDownlaoderOperation或者NSOperation的cancel方法

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
/**
通过这个对象关联一个`SDWebImageDownloaderOperation`对象
*/
@interface SDWebImageCombinedOperation : NSObject <SDWebImageOperation>

/**
用于判断Operation是否已经取消
*/
@property (assign, nonatomic, getter = isCancelled) BOOL cancelled;

/**
取消回调
*/
@property (copy, nonatomic, nullable) SDWebImageNoParamsBlock cancelBlock;

/**
SDWebImageDownloaderOperation对象。可以通过这个属性取消一个NSOperation
*/
@property (strong, nonatomic, nullable) NSOperation *cacheOperation;

@end

@implementation SDWebImageCombinedOperation

/**
取消Operation的回调Block

@param cancelBlock 回调Block
*/
- (void)setCancelBlock:(nullable SDWebImageNoParamsBlock)cancelBlock {
// check if the operation is already cancelled, then we just call the cancelBlock
if (self.isCancelled) {
if (cancelBlock) {
cancelBlock();
}
_cancelBlock = nil; // don't forget to nil the cancelBlock, otherwise we will get crashes
} else {
_cancelBlock = [cancelBlock copy];
}
}

/**
调用cancel方法。这个方法继承自`SDWebImageOperation`协议。方法里面会调用`SDWebImageDownlaoderOperation`或者`NSOperation`的cancel方法
*/
- (void)cancel {
self.cancelled = YES;
if (self.cacheOperation) {
//调用`SDWebImageDownlaoderOperation`或者`NSOperation`的cancel方法
[self.cacheOperation cancel];
self.cacheOperation = nil;
}
if (self.cancelBlock) {
self.cancelBlock();

// TODO: this is a temporary fix to #809.
// Until we can figure the exact cause of the crash, going with the ivar instead of the setter
// self.cancelBlock = nil;
_cancelBlock = nil;
}
}

@end

那么取消一个Operation是在哪里呢?其实就在UIView+WebCache中,所有使用SDWebImage的View都可以使用这个方法来取消下载Operation:

1
2
3
4
5
6
/**
取消当前Class对应的所有加载请求
*/
- (void)sd_cancelCurrentImageLoad {
[self sd_cancelImageLoadOperationWithKey:NSStringFromClass([self class])];
}

上面这个方法又会调用UIView+WebCacheOperation分类的sd_cancelImageLoadOperationWithKey方法来实现,这个方法会调用SDWebImageCombinedOperation对象的cancel方法,然后在cancel方法中再调用SDWebImageDownloadOperation或者NSOperationcancel方法:

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
/**
取消当前key对应的所有`SDWebImageCombinedOperation`对象

@param key Operation对应的key
*/
- (void)sd_cancelImageLoadOperationWithKey:(nullable NSString *)key {
// Cancel in progress downloader from queue
//获取当前View对应的所有key
SDOperationsDictionary *operationDictionary = [self operationDictionary];
//获取对应的图片加载Operation
id operations = operationDictionary[key];
//取消所有当前View对应的所有Operation
if (operations) {
if ([operations isKindOfClass:[NSArray class]]) {
for (id <SDWebImageOperation> operation in operations) {
if (operation) {
//SDWebImageCombinedOperation对象的cancel方法
[operation cancel];
}
}
} else if ([operations conformsToProtocol:@protocol(SDWebImageOperation)]){
[(id<SDWebImageOperation>) operations cancel];
}
[operationDictionary removeObjectForKey:key];
}
}

3 SDImageCache分析

从上面的代码中,发现SDWebImageManager通过SDImageCache来获取/存储图片,而且SDImageCache是一个单列对象。他的具体实现可以总结为如下几点:

  • 通过AutoPurgeCache这个NSCache子类来管理内存缓存。当接收到内存警告的时候,移除内存缓存的所有对象。
  • 接收到UIApplicationDidReceiveMemoryWarningNotification通知以后,会删除内存中缓存的图片。
  • 接收到UIApplicationWillTerminateNotification通知以后,会通过deleteOldFiles方法删除老的图片。具体删除规则如下:
    • 缓存大小、过期日期、是否解压缩缓存、是否允许内存缓存都是通过SDImageCacheConfig这个对象来配置的。
    • 首先会迭代缓存目录下的所有文件,对于大于一周的图片数据全部删除。
    • 然后会记录缓存目录的所有大小,如果当前缓存大于默认缓存,则按照创建日期开始删除图片缓存,直到缓存大小小于默认缓存大小。
  • 当接收到UIApplicationDidEnterBackgroundNotification通知以后,会调用deleteOldFilesWithCompletionBlock来清理缓存数据。
  • 定义了一些列方法来处理图片的获取、缓存、移除操作。主要有下面几个方法:
    • queryCacheOperationForKey查询指定key对应的缓存图片,先从内存查找,然后从磁盘查找。
    • removeImageForKey移除指定的缓存图片。
    • diskImageDataBySearchingAllPathsForKey在磁盘上查找指定key对应的图片。
    • storeImageDataToDisk把指定的图片数据存入磁盘。
  • 通过cachedFileNameForKey方法获取一张图片对应的MD5加密的缓存名字。

SDImageCache的完整实现如下:

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
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
@interface AutoPurgeCache : NSCache
@end

/**
如果接收到内存警告、移除所有的缓存对象
*/
@implementation AutoPurgeCache

- (nonnull instancetype)init {
self = [super init];
if (self) {
#if SD_UIKIT
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(removeAllObjects) name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
#endif
}
return self;
}

- (void)dealloc {
#if SD_UIKIT
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
#endif
}

@end
/**
计算一张图片占用内存的大小

@param image 图片
@return 占用内存大小
*/
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
}
/**
单列对象、用处管理图片的缓存以及缓存图片的处理
*/
@interface SDImageCache ()

#pragma mark - Properties
@property (strong, nonatomic, nonnull) NSCache *memCache;
@property (strong, nonatomic, nonnull) NSString *diskCachePath;
@property (strong, nonatomic, nullable) NSMutableArray<NSString *> *customPaths;
@property (SDDispatchQueueSetterSementics, nonatomic, nullable) dispatch_queue_t ioQueue;

@end

@implementation SDImageCache {
NSFileManager *_fileManager;
}
#pragma mark - Singleton, init, dealloc
+ (nonnull instancetype)sharedImageCache {
static dispatch_once_t once;
static id instance;
dispatch_once(&once, ^{
instance = [self new];
});
return instance;
}

- (instancetype)init {
return [self initWithNamespace:@"default"];
}

- (nonnull instancetype)initWithNamespace:(nonnull NSString *)ns {
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];
// 初始化一个串行的dispatch_queue_t
_ioQueue = dispatch_queue_create("com.hackemist.SDWebImageCache", DISPATCH_QUEUE_SERIAL);
//初始化缓存策略配置对象
_config = [[SDImageCacheConfig alloc] init];

// 初始化内存缓存对象
_memCache = [[AutoPurgeCache alloc] init];
_memCache.name = fullNamespace;

// 初始化磁盘缓存路径
if (directory != nil) {
_diskCachePath = [directory stringByAppendingPathComponent:fullNamespace];
} else {
NSString *path = [self makeDiskCachePath:ns];
_diskCachePath = path;
}

dispatch_sync(_ioQueue, ^{
_fileManager = [NSFileManager new];
});

#if SD_UIKIT
/*
当应用收到内存警告的时候,清除内存缓存。
*/
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(clearMemory)
name:UIApplicationDidReceiveMemoryWarningNotification
object:nil];
/*
当应用终止的时候,清除老数据
*/
[[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];
SDDispatchQueueRelease(_ioQueue);
}

/**
当前queue是否在ioQueue
*/
- (void)checkIfQueueIsIOQueue {
const char *currentQueueLabel = dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL);
const char *ioQueueLabel = dispatch_queue_get_label(self.ioQueue);
if (strcmp(currentQueueLabel, ioQueueLabel) != 0) {
NSLog(@"This method should be called from the ioQueue");
}
}

#pragma mark - Cache paths
/*
添加只能读的缓存目录
*/
- (void)addReadOnlyCachePath:(nonnull NSString *)path {
if (!self.customPaths) {
self.customPaths = [NSMutableArray new];
}

if (![self.customPaths containsObject:path]) {
[self.customPaths addObject:path];
}
}

/**
获取指定key对应的完整缓存路径

@param key key,对应一张图片。比如图片的名字
@param path 指定根目录
@return 完整目录
*/
- (nullable NSString *)cachePathForKey:(nullable NSString *)key inPath:(nonnull NSString *)path {
NSString *filename = [self cachedFileNameForKey:key];
return [path stringByAppendingPathComponent:filename];
}

- (nullable NSString *)defaultCachePathForKey:(nullable NSString *)key {
return [self cachePathForKey:key inPath:self.diskCachePath];
}


/**
MD5加密
X 表示以十六进制形式输出
02 表示不足两位,前面补0输出;出过两位,不影响

@param key key
@return 加密后的数据
*/
- (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);
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], [key.pathExtension isEqualToString:@""] ? @"" : [NSString stringWithFormat:@".%@", key.pathExtension]];

return filename;
}

/**
图片缓存目录

@param fullNamespace 自定义目录
@return 完整目录
*/
- (nullable NSString *)makeDiskCachePath:(nonnull NSString*)fullNamespace {
NSArray<NSString *> *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
return [paths[0] stringByAppendingPathComponent:fullNamespace];
}

#pragma mark - 图片缓存具体实现的一些列方法

- (void)storeImage:(nullable UIImage *)image
forKey:(nullable NSString *)key
completion:(nullable SDWebImageNoParamsBlock)completionBlock {
[self storeImage:image imageData:nil forKey:key toDisk:YES completion: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];
}

/**
把一张图片存入缓存的具体实现

@param image 缓存的图片对象
@param imageData 缓存的图片数据
@param key 缓存对应的key
@param toDisk 是否缓存到瓷片
@param completionBlock 缓存完成回调
*/
- (void)storeImage:(nullable UIImage *)image
imageData:(nullable NSData *)imageData
forKey:(nullable NSString *)key
toDisk:(BOOL)toDisk
completion:(nullable SDWebImageNoParamsBlock)completionBlock {
if (!image || !key) {
if (completionBlock) {
completionBlock();
}
return;
}
//缓存到内存
if (self.config.shouldCacheImagesInMemory) {
//计算缓存数据的大小
NSUInteger cost = SDCacheCostForImage(image);
//加入缓存对此昂
[self.memCache setObject:image forKey:key cost:cost];
}

if (toDisk) {
/*
在一个线性队列中做磁盘缓存操作。
*/
dispatch_async(self.ioQueue, ^{
NSData *data = imageData;
if (!data && image) {
//获取图片的类型GIF/PNG等
SDImageFormat imageFormatFromData = [NSData sd_imageFormatForImageData:data];
//根据指定的SDImageFormat。把图片转换为对应的data数据
data = [image sd_imageDataAsFormat:imageFormatFromData];
}
//把处理好了的数据存入磁盘
[self storeImageDataToDisk:data forKey:key];
if (completionBlock) {
dispatch_async(dispatch_get_main_queue(), ^{
completionBlock();
});
}
});
} else {
if (completionBlock) {
completionBlock();
}
}
}

/**
把图片资源存入磁盘

@param imageData 图片数据
@param key key
*/
- (void)storeImageDataToDisk:(nullable NSData *)imageData forKey:(nullable NSString *)key {
if (!imageData || !key) {
return;
}

[self checkIfQueueIsIOQueue];
//缓存目录是否已经初始化
if (![_fileManager fileExistsAtPath:_diskCachePath]) {
[_fileManager createDirectoryAtPath:_diskCachePath withIntermediateDirectories:YES attributes:nil error:NULL];
}

// get cache Path for image key
//获取key对应的完整缓存路径
NSString *cachePathForKey = [self defaultCachePathForKey:key];
// transform to NSUrl
NSURL *fileURL = [NSURL fileURLWithPath:cachePathForKey];
//把数据存入路径
[_fileManager createFileAtPath:cachePathForKey contents:imageData attributes:nil];

// disable iCloud backup
if (self.config.shouldDisableiCloud) {
//给文件添加到运行存储到iCloud属性
[fileURL setResourceValue:@YES forKey:NSURLIsExcludedFromBackupKey error:nil];
}
}

#pragma mark - Query and Retrieve Ops

- (void)diskImageExistsWithKey:(nullable NSString *)key completion:(nullable SDWebImageCheckCacheCompletionBlock)completionBlock {
dispatch_async(_ioQueue, ^{
BOOL exists = [_fileManager fileExistsAtPath:[self defaultCachePathForKey:key]];

// fallback because of https://github.com/rs/SDWebImage/pull/976 that added the extension to the disk file name
// checking the key with and without the extension
if (!exists) {
exists = [_fileManager fileExistsAtPath:[self defaultCachePathForKey:key].stringByDeletingPathExtension];
}

if (completionBlock) {
dispatch_async(dispatch_get_main_queue(), ^{
completionBlock(exists);
});
}
});
}

/**
根据key获取缓存在内存中的图片

@param key key
@return 缓存的图片
*/
- (nullable UIImage *)imageFromMemoryCacheForKey:(nullable NSString *)key {
return [self.memCache objectForKey:key];
}

- (nullable UIImage *)imageFromDiskCacheForKey:(nullable NSString *)key {
UIImage *diskImage = [self diskImageForKey:key];
if (diskImage && self.config.shouldCacheImagesInMemory) {
NSUInteger cost = SDCacheCostForImage(diskImage);
[self.memCache setObject:diskImage forKey:key cost:cost];
}

return diskImage;
}

- (nullable UIImage *)imageFromCacheForKey:(nullable NSString *)key {
// First check the in-memory cache...
UIImage *image = [self imageFromMemoryCacheForKey:key];
if (image) {
return image;
}

// Second check the disk cache...
image = [self imageFromDiskCacheForKey:key];
return image;
}

/**
根据指定的key,获取存储在磁盘上的数据

@param key 图片对应的key
@return 返回图片数据
*/
- (nullable NSData *)diskImageDataBySearchingAllPathsForKey:(nullable NSString *)key {
//获取key对应的path
NSString *defaultPath = [self defaultCachePathForKey:key];
NSData *data = [NSData dataWithContentsOfFile:defaultPath];
if (data) {
return data;
}

// fallback because of https://github.com/rs/SDWebImage/pull/976 that added the extension to the disk file name
// checking the key with and without the extension
/*
如果key么有后缀名,则会走到这里通过这里读取
*/
data = [NSData dataWithContentsOfFile:defaultPath.stringByDeletingPathExtension];
if (data) {
return data;
}
/*
如果在默认路径没有找到图片,则在自定义路径迭代查找
*/
NSArray<NSString *> *customPaths = [self.customPaths copy];
for (NSString *path in customPaths) {
NSString *filePath = [self cachePathForKey:key inPath:path];
NSData *imageData = [NSData dataWithContentsOfFile:filePath];
if (imageData) {
return imageData;
}

// fallback because of https://github.com/rs/SDWebImage/pull/976 that added the extension to the disk file name
// checking the key with and without the extension
imageData = [NSData dataWithContentsOfFile:filePath.stringByDeletingPathExtension];
if (imageData) {
return imageData;
}
}

return nil;
}

/**
根据指定的key获取image对象

@param key key
@return image对象
*/
- (nullable UIImage *)diskImageForKey:(nullable NSString *)key {
//获取磁盘数据
NSData *data = [self diskImageDataBySearchingAllPathsForKey:key];
if (data) {
UIImage *image = [UIImage sd_imageWithData:data];
image = [self scaledImageForKey:key image:image];
if (self.config.shouldDecompressImages) {
image = [UIImage decodedImageWithImage:image];
}
return image;
}
else {
return nil;
}
}

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

/**
在缓存中查询对应key的数据。通过一个NSOperation来完成

@param key 要查询的key
@param doneBlock 查询结束以后的Block
@return 返回做查询操作的Block
*/
- (nullable NSOperation *)queryCacheOperationForKey:(nullable NSString *)key done:(nullable SDCacheQueryCompletedBlock)doneBlock {
if (!key) {
if (doneBlock) {
doneBlock(nil, nil, SDImageCacheTypeNone);
}
return nil;
}
// First check the in-memory cache...
//首先从内测中查找图片
UIImage *image = [self imageFromMemoryCacheForKey:key];
if (image) {
NSData *diskData = nil;
//是否是gif图片
if ([image isGIF]) {
diskData = [self diskImageDataBySearchingAllPathsForKey:key];
}
if (doneBlock) {
doneBlock(image, diskData, SDImageCacheTypeMemory);
}
return nil;
}
//新建一个NSOperation来获取磁盘图片
NSOperation *operation = [NSOperation new];
dispatch_async(self.ioQueue, ^{
if (operation.isCancelled) {
// do not call the completion if cancelled
return;
}
//在一个自动释放池中处理图片从磁盘加载
@autoreleasepool {
NSData *diskData = [self diskImageDataBySearchingAllPathsForKey:key];
UIImage *diskImage = [self diskImageForKey:key];
if (diskImage && self.config.shouldCacheImagesInMemory) {
NSUInteger cost = SDCacheCostForImage(diskImage);
//把从磁盘取出的缓存图片加入内存缓存中
[self.memCache setObject:diskImage forKey:key cost:cost];
}
//图片处理完成以后回调Block
if (doneBlock) {
dispatch_async(dispatch_get_main_queue(), ^{
doneBlock(diskImage, diskData, SDImageCacheTypeDisk);
});
}
}
});
return operation;
}

#pragma mark - Remove Ops

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

/**
移除指定key对应的缓存数据

@param key key
@param fromDisk 是否也清除磁盘缓存
@param completion 回调
*/
- (void)removeImageForKey:(nullable NSString *)key fromDisk:(BOOL)fromDisk withCompletion:(nullable SDWebImageNoParamsBlock)completion {
if (key == nil) {
return;
}
//移除内存缓存
if (self.config.shouldCacheImagesInMemory) {
[self.memCache removeObjectForKey:key];
}
//移除磁盘缓存
if (fromDisk) {
dispatch_async(self.ioQueue, ^{
[_fileManager removeItemAtPath:[self defaultCachePathForKey:key] error:nil];

if (completion) {
dispatch_async(dispatch_get_main_queue(), ^{
completion();
});
}
});
} else if (completion){
completion();
}

}

# pragma mark - Mem Cache settings

- (void)setMaxMemoryCost:(NSUInteger)maxMemoryCost {
self.memCache.totalCostLimit = maxMemoryCost;
}

- (NSUInteger)maxMemoryCost {
return self.memCache.totalCostLimit;
}

- (NSUInteger)maxMemoryCountLimit {
return self.memCache.countLimit;
}

- (void)setMaxMemoryCountLimit:(NSUInteger)maxCountLimit {
self.memCache.countLimit = maxCountLimit;
}

#pragma mark - 内存缓存清理相关操作

/**
清理当前SDImageCache对象的内存缓存
*/
- (void)clearMemory {
[self.memCache removeAllObjects];
}

/**
移除所有的缓存图片数据

@param completion 移除完成以后回调
*/
- (void)clearDiskOnCompletion:(nullable SDWebImageNoParamsBlock)completion {
dispatch_async(self.ioQueue, ^{
[_fileManager removeItemAtPath:self.diskCachePath error:nil];
[_fileManager createDirectoryAtPath:self.diskCachePath
withIntermediateDirectories:YES
attributes:nil
error:NULL];
if (completion) {
dispatch_async(dispatch_get_main_queue(), ^{
completion();
});
}
});
}

- (void)deleteOldFiles {
[self deleteOldFilesWithCompletionBlock:nil];
}

/**
当应用终止或者进入后台都回调用这个方法来清除缓存图片。
这里会根据图片存储时间来清理图片、默认是一周,从最老的图片开始清理。如果图片缓存空间小于一个规定值,则不考虑。

@param completionBlock 清除完成以后的回调
*/
- (void)deleteOldFilesWithCompletionBlock:(nullable SDWebImageNoParamsBlock)completionBlock {
dispatch_async(self.ioQueue, ^{
//获取磁盘缓存的默认根目录
NSURL *diskCacheURL = [NSURL fileURLWithPath:self.diskCachePath isDirectory:YES];

NSArray<NSString *> *resourceKeys = @[NSURLIsDirectoryKey, NSURLContentModificationDateKey, NSURLTotalFileAllocatedSizeKey];

// This enumerator prefetches useful properties for our cache files.
/*
第二个参数制定了需要获取的属性集合
第三个参数表示不迭代隐藏文件
*/
NSDirectoryEnumerator *fileEnumerator = [_fileManager enumeratorAtURL:diskCacheURL
includingPropertiesForKeys:resourceKeys
options:NSDirectoryEnumerationSkipsHiddenFiles
errorHandler:NULL];

NSDate *expirationDate = [NSDate dateWithTimeIntervalSinceNow:-self.config.maxCacheAge];
NSMutableDictionary<NSURL *, NSDictionary<NSString *, id> *> *cacheFiles = [NSMutableDictionary dictionary];
NSUInteger currentCacheSize = 0;

// Enumerate all of the files in the cache directory. This loop has two purposes:
//
// 1. Removing files that are older than the expiration date.
// 2. Storing file attributes for the size-based cleanup pass.
/*
迭代缓存目录。有两个目的:
1 删除比指定日期更老的图片
2 记录文件的大小,以提供给后面删除使用
*/
NSMutableArray<NSURL *> *urlsToDelete = [[NSMutableArray alloc] init];
for (NSURL *fileURL in fileEnumerator) {
NSError *error;
//获取指定url对应文件的指定三种属性的key和value
NSDictionary<NSString *, id> *resourceValues = [fileURL resourceValuesForKeys:resourceKeys error:&error];
// Skip directories and errors.
//如果是文件夹则返回
if (error || !resourceValues || [resourceValues[NSURLIsDirectoryKey] boolValue]) {
continue;
}

// Remove files that are older than the expiration date;
//获取指定url文件对应的修改日期
NSDate *modificationDate = resourceValues[NSURLContentModificationDateKey];
//如果修改日期大于指定日期,则加入要移除的数组里
if ([[modificationDate laterDate:expirationDate] isEqualToDate:expirationDate]) {
[urlsToDelete addObject:fileURL];
continue;
}

// Store a reference to this file and account for its total size.
//获取指定的url对应的文件的大小,并且把url与对应大小存入一个字典中
NSNumber *totalAllocatedSize = resourceValues[NSURLTotalFileAllocatedSizeKey];
currentCacheSize += totalAllocatedSize.unsignedIntegerValue;
cacheFiles[fileURL] = resourceValues;
}
//删除所有最后修改日期大于指定日期的所有文件
for (NSURL *fileURL in urlsToDelete) {
[_fileManager removeItemAtURL:fileURL error:nil];
}

// If our remaining disk cache exceeds a configured maximum size, perform a second
// size-based cleanup pass. We delete the oldest files first.
/*
如果我们当前缓存的大小超过了默认大小,则按照日期删除,直到缓存大小<默认大小的一半
*/
if (self.config.maxCacheSize > 0 && currentCacheSize > self.config.maxCacheSize) {
// Target half of our maximum cache size for this cleanup pass.
const NSUInteger desiredCacheSize = self.config.maxCacheSize / 2;

// Sort the remaining cache files by their last modification time (oldest first).
//根据文件创建的时间排序
NSArray<NSURL *> *sortedFiles = [cacheFiles keysSortedByValueWithOptions:NSSortConcurrent
usingComparator:^NSComparisonResult(id obj1, id obj2) {
return [obj1[NSURLContentModificationDateKey] compare:obj2[NSURLContentModificationDateKey]];
}];

// Delete files until we fall below our desired cache size.
/*
迭代删除缓存,直到缓存大小是默认缓存大小的一半
*/
for (NSURL *fileURL in sortedFiles) {
if ([_fileManager removeItemAtURL:fileURL error:nil]) {
NSDictionary<NSString *, id> *resourceValues = cacheFiles[fileURL];
NSNumber *totalAllocatedSize = resourceValues[NSURLTotalFileAllocatedSizeKey];
//总得缓存大小减去当前要删除文件的大小
currentCacheSize -= totalAllocatedSize.unsignedIntegerValue;

if (currentCacheSize < desiredCacheSize) {
break;
}
}
}
}
//执行完毕,主线程回调
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:^{
// Clean up any unfinished task business by marking where you
// stopped or ending the task outright.
//当任务非正常终止的时候,做清理工作
[application endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}];

// Start the long-running task and return immediately.
//图片清理结束以后。处理完成
[self deleteOldFilesWithCompletionBlock:^{
//清理完成以后,终止任务
[application endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}];
}
#endif

#pragma mark - Cache Info

- (NSUInteger)getSize {
__block NSUInteger size = 0;
dispatch_sync(self.ioQueue, ^{
NSDirectoryEnumerator *fileEnumerator = [_fileManager enumeratorAtPath:self.diskCachePath];
for (NSString *fileName in fileEnumerator) {
NSString *filePath = [self.diskCachePath stringByAppendingPathComponent:fileName];
NSDictionary<NSString *, id> *attrs = [[NSFileManager defaultManager] attributesOfItemAtPath:filePath error:nil];
size += [attrs fileSize];
}
});
return size;
}

- (NSUInteger)getDiskCount {
__block NSUInteger count = 0;
dispatch_sync(self.ioQueue, ^{
NSDirectoryEnumerator *fileEnumerator = [_fileManager enumeratorAtPath:self.diskCachePath];
count = fileEnumerator.allObjects.count;
});
return count;
}

- (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 = [_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);
});
}
});
}
@end

最后原文地址.html),demo地址


...

...

00:00
00:00