对具体代码工程、方法业务流程及相关独立的工具类分析后,接下来主要解读几大核心步骤如何调起协调缓存和下载的,缓存的逻辑细节过程及下载的核心过程。主要分析方式还是按照习惯性读代码一个方法一个方法的调用步骤,再具体到每一个类进行解析;

SDWebImageManager

UIWebImageManager 类是整个的核心管理类,主要负责发起 SDImageCache 图片缓存相关操作及 SDWebImageDownloader 图片下载操作;

UIImageView 对象通过调用 UIImageView+WebCache sd_setImageWithURL:等接口方法,内部再都会调用自身的 sd_setImageWithURL: placeholderImage: options: progress: completed: 方法,在这个方法里会调用 SDWebImageManager 的核心方法 downloadImageWithURL: options: progress: completed: ; SDWebImageManager 的这个核心方法作用是根据 URL,从 SDImageCache 查看是否有缓存,缓存是否有效,如果可以使用缓存,直接将缓存的数据返回;缓存没有想要的数据,则用 SDWebImageDownloader 去下载图片,这个下载器,设置了 HTTP 相应的缓存策略,使用 SDImageCache 中缓存策略也用了跟 HTTP 相应的缓存策略,这样可以复用 HTTP 的缓存策略;

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
- (id <SDWebImageOperation>)downloadImageWithURL:(NSURL *)url
options:(SDWebImageOptions)options
progress:(SDWebImageDownloaderProgressBlock)progressBlock
completed:(SDWebImageCompletionWithFinishedBlock)completedBlock {

// 根据 url 获取 url 对应的 key
NSString *key = [self cacheKeyForURL:url];

// 5.
operation.cacheOperation = [self.imageCache queryDiskCacheForKey:key done:^(UIImage *image, SDImageCacheType cacheType) {
if (operation.isCancelled) {
@synchronized (self.runningOperations) {
[self.runningOperations removeObject:operation];
}

return;
}

if ((!image || options & SDWebImageRefreshCached) && (![self.delegate respondsToSelector:@selector(imageManager:shouldDownloadImageForURL:)] || [self.delegate imageManager:self shouldDownloadImageForURL:url])) {
if (image && options & SDWebImageRefreshCached) {
dispatch_main_sync_safe(^{
// If image was found in the cache but SDWebImageRefreshCached is provided, notify about the cached image
// AND try to re-download it in order to let a chance to NSURLCache to refresh it from server.
completedBlock(image, nil, cacheType, YES, url);
});
}

// download if no image or requested to refresh anyway, and download allowed by delegate
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 (image && 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;
}
// 调起下载图片的下载器
id <SDWebImageOperation> subOperation = [self.imageDownloader downloadImageWithURL:url options:downloaderOptions progress:progressBlock completed:^(UIImage *downloadedImage, NSData *data, NSError *error, BOOL finished) {
__strong __typeof(weakOperation) strongOperation = weakOperation;
if (!strongOperation || strongOperation.isCancelled) {
// Do nothing if the operation was cancelled
// See #699 for more details
// if we would call the completedBlock, there could be a race condition between this block and another completedBlock for the same object, so if this one is called second, we will overwrite the new data
}
else if (error) {
dispatch_main_sync_safe(^{
if (strongOperation && !strongOperation.isCancelled) {
completedBlock(nil, error, SDImageCacheTypeNone, finished, url);
}
});

if ( error.code != NSURLErrorNotConnectedToInternet
&& error.code != NSURLErrorCancelled
&& error.code != NSURLErrorTimedOut
&& error.code != NSURLErrorInternationalRoamingOff
&& error.code != NSURLErrorDataNotAllowed
&& error.code != NSURLErrorCannotFindHost
&& error.code != NSURLErrorCannotConnectToHost) {
@synchronized (self.failedURLs) {
[self.failedURLs addObject:url];
}
}
}
else {
if ((options & SDWebImageRetryFailed)) {
@synchronized (self.failedURLs) {
[self.failedURLs removeObject:url];
}
}

BOOL cacheOnDisk = !(options & SDWebImageCacheMemoryOnly);

if (options & SDWebImageRefreshCached && image && !downloadedImage) {
// Image refresh hit the NSURLCache cache, do not call the completion block
}
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), ^{
UIImage *transformedImage = [self.delegate imageManager:self transformDownloadedImage:downloadedImage withURL:url];

if (transformedImage && finished) {
BOOL imageWasTransformed = ![transformedImage isEqual:downloadedImage];
[self.imageCache storeImage:transformedImage recalculateFromImage:imageWasTransformed imageData:(imageWasTransformed ? nil : data) forKey:key toDisk:cacheOnDisk];
}

dispatch_main_sync_safe(^{
if (strongOperation && !strongOperation.isCancelled) {
completedBlock(transformedImage, nil, SDImageCacheTypeNone, finished, url);
}
});
});
}
else {
if (downloadedImage && finished) {
[self.imageCache storeImage:downloadedImage recalculateFromImage:NO imageData:data forKey:key toDisk:cacheOnDisk];
}

dispatch_main_sync_safe(^{
if (strongOperation && !strongOperation.isCancelled) {
completedBlock(downloadedImage, nil, SDImageCacheTypeNone, finished, url);
}
});
}
}

if (finished) {
@synchronized (self.runningOperations) {
if (strongOperation) {
[self.runningOperations removeObject:strongOperation];
}
}
}
}];
operation.cancelBlock = ^{
[subOperation cancel];

@synchronized (self.runningOperations) {
__strong __typeof(weakOperation) strongOperation = weakOperation;
if (strongOperation) {
[self.runningOperations removeObject:strongOperation];
}
}
};
}
else if (image) { // 有缓存的图片
dispatch_main_sync_safe(^{
__strong __typeof(weakOperation) strongOperation = weakOperation;
if (strongOperation && !strongOperation.isCancelled) {
completedBlock(image, nil, cacheType, YES, url);
}
});
@synchronized (self.runningOperations) {
[self.runningOperations removeObject:operation];
}
}
else { // 没有缓存并且请求被放弃
// Image not in cache and download disallowed by delegate
dispatch_main_sync_safe(^{
__strong __typeof(weakOperation) strongOperation = weakOperation;
if (strongOperation && !weakOperation.isCancelled) {
completedBlock(nil, nil, SDImageCacheTypeNone, YES, url);
}
});
@synchronized (self.runningOperations) {
[self.runningOperations removeObject:operation];
}
}
}];

return operation;
}
  1. imageCache 调用 SDImageCache 的 queryDiskCacheForKey: done: 方法,查询是否有缓存,有缓存直接 block 返回;
  2. 没有缓存,有请求,imageDownloader 调用 SDWebImageDownloader 的 downloadImageWithURL: options: progress: completed: 方法发起下载操作,将下载结果 block 返回,再 imageCache 调用 SDImageCache 的 storeImage: recalculateFromImage: imageData: forKey: toDisk: 方法进行图片缓存;
  3. 没有缓存并且没有请求,也 block 返回;

SDImageCache

SDImageCache 继承 NSObject,主要功能:存储操作,memory 内存缓存、disk 磁盘缓存;取操作,从缓存中取图片;删除操作,内存中删除和磁盘中删除等操作;

1. 存储图片四个方法

  • storeImage: forKey: 根据 key 保存图片到内存和磁盘;
  • storeImage: forKey: toDisk: 根据 key 保存图片到内存,磁盘(可选);
  • storeImage: recalculateFromImage: imageData: forKey: toDisk: 根据 key 保存图片和 imageData 到内存,磁盘(可选);
  • storeImageDataToDisk: forKey: 根据 key 保存图片 imageData 到磁盘;

上述四个方法内部都将调用如下方法进行缓存:

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
// 保存图片和 imageData 到磁盘和内存中
- (void)storeImage:(UIImage *)image recalculateFromImage:(BOOL)recalculate imageData:(NSData *)imageData forKey:(NSString *)key toDisk:(BOOL)toDisk {
if (!image || !key) {
return;
}

// 缓存到内存
if (self.shouldCacheImagesInMemory) {
NSUInteger cost = SDCacheCostForImage(image);
[self.memCache setObject:image forKey:key cost:cost];
}

// 缓存到磁盘,采用异步操作
if (toDisk) {
dispatch_async(self.ioQueue, ^{
NSData *data = imageData;

if (image && (recalculate || !data)) {
// PNG图片有统一的格式,前8个字节通常包含:137 80 78 71 13 10 26 10
// 如图片的imageData为空(如果试图直接保存一个UIImage 或者 图片是由下载转换得来),且图片含有alpha通道,将进行PNG缓存处理避免失去透明度

int alphaInfo = CGImageGetAlphaInfo(image.CGImage);
BOOL hasAlpha = !(alphaInfo == kCGImageAlphaNone ||
alphaInfo == kCGImageAlphaNoneSkipFirst ||
alphaInfo == kCGImageAlphaNoneSkipLast);
BOOL imageIsPng = hasAlpha;

// But if we have an image data, we will look at the preffix
if ([imageData length] >= [kPNGSignatureData length]) {
imageIsPng = ImageDataHasPNGPreffix(imageData);
}

if (imageIsPng) {
data = UIImagePNGRepresentation(image);
}
else {
data = UIImageJPEGRepresentation(image, (CGFloat)1.0);
}
#else
data = [NSBitmapImageRep representationOfImageRepsInArray:image.representations usingType: NSJPEGFileType properties:nil];
#endif
}

[self storeImageDataToDisk:data forKey:key];
});
}
}

// 保存图片imageData到磁盘
- (void)storeImageDataToDisk:(NSData *)imageData forKey:(NSString *)key {
if (!imageData) {
return;
}

// 如果不存在该路径,则创建
if (![_fileManager fileExistsAtPath:_diskCachePath]) {
[_fileManager createDirectoryAtPath:_diskCachePath withIntermediateDirectories:YES attributes:nil error:NULL];
}

// 根据key获取缓存路径:get cache Path for image key
NSString *cachePathForKey = [self defaultCachePathForKey:key];
// 将缓存路径转换为URL:transform to NSUrl
// transform to NSUrl
NSURL *fileURL = [NSURL fileURLWithPath:cachePathForKey];

// 保存数据
[_fileManager createFileAtPath:cachePathForKey contents:imageData attributes:nil];

// 关闭iCloud备份:disable iCloud backup
if (self.shouldDisableiCloud) {
[fileURL setResourceValue:[NSNumber numberWithBool:YES] forKey:NSURLIsExcludedFromBackupKey error:nil];
}
}

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

// 返回缓存完整路径,其中文件名是根据 key 值生成的MD5值
- (NSString *)cachePathForKey:(NSString *)key inPath:(NSString *)path {
NSString *filename = [self cachedFileNameForKey:key];
return [path stringByAppendingPathComponent:filename];
}

// 根据key值生成文件名:采用MD5
- (NSString *)cachedFileNameForKey:(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);
// 保存缓存文件的文件名 : url做了md5的处理 + .扩展名
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;
}

2. 查找图片三个方法

  • (NSOperation *)queryDiskCacheForKey: done: 据 key 判断 Image 是否存在磁盘中,异步处理;
  • imageFromMemoryCacheForKey: 根据 key 从内存中获取图片;
  • imageFromDiskCacheForKey: 根据 key 从磁盘缓存中获取图片但是会先查询内存中是否有,有直接返回;

此处分析 SDWebImageManager 中调用的 imageCache 方法 queryDiskCacheForKey: done:,在缓存中查询对应key的数据,返回一个 NSOperation 对象,查询的图片结果 block 中回调出去;

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
// 根据key从磁盘缓存中获取图片:异步操作
- (NSOperation *)queryDiskCacheForKey:(NSString *)key done:(SDWebImageQueryCompletedBlock)doneBlock {
if (!doneBlock) {
return nil;
}

if (!key) {
doneBlock(nil, SDImageCacheTypeNone);
return nil;
}

// 首先从内存中查找图片
UIImage *image = [self imageFromMemoryCacheForKey:key];
if (image) {
doneBlock(image, SDImageCacheTypeMemory);
return nil;
}

// 新建一个 NSOperation 对象来获取磁盘图片
NSOperation *operation = [NSOperation new];
dispatch_async(self.ioQueue, ^{
if (operation.isCancelled) {
return;
}

@autoreleasepool {
UIImage *diskImage = [self diskImageForKey:key];
if (diskImage && self.shouldCacheImagesInMemory) {
NSUInteger cost = SDCacheCostForImage(diskImage);
// 把从磁盘取出的缓存图片加入内存缓存中
[self.memCache setObject:diskImage forKey:key cost:cost];
}

dispatch_async(dispatch_get_main_queue(), ^{
doneBlock(diskImage, SDImageCacheTypeDisk);
});
}
});

return operation;
}

3. 删除图片清理磁盘和内存

  1. 监听内存警告通知 UIApplicationDidReceiveMemoryWarningNotification 清理内存;
  2. 应用在前台终止时 和 应用进入后台时清理磁盘,清除磁盘策略是目标最大缓存的一半;
  3. 应用处于后台,清理过期的图片默认时间一周,过期的清除磁盘缓存;
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

// SDImageCache init 初始化方法中监听通知

// 内存警告时,清理内存 对应步骤一
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(clearMemory)
name:UIApplicationDidReceiveMemoryWarningNotification
object:nil];

// 应用在前台终止时,清理磁盘
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(cleanDisk)
name:UIApplicationWillTerminateNotification
object:nil];

// 应用进入后台时,清理磁盘
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(backgroundCleanDisk)
name:UIApplicationDidEnterBackgroundNotification
object:nil];
// 清空内存缓存
- (void)clearMemory {
[self.memCache removeAllObjects];
}

// 步骤二和三执行清理方法最终都是调用下面方法
// 清除磁盘缓存,异步操作:1、清除过期的缓存文件;2、缓存超过设定阈值时,按时间顺序删除缓存中的图片,直到缓存剩余空间达阈值的一半
- (void)cleanDiskWithCompletionBlock:(SDWebImageNoParamsBlock)completionBlock {
dispatch_async(self.ioQueue, ^{
// 1. 找磁盘缓存目录
NSURL *diskCacheURL = [NSURL fileURLWithPath:self.diskCachePath isDirectory:YES];
/* 2.
NSURLIsDirectoryKey, 目录key
NSURLContentModificationDateKey, url指向的内容,最后修改时间的key
NSURLTotalFileAllocatedSizeKey 文件总大小的key
// resourceKeys包含要了解的属性:允许判断遍历到的URL所指对象是否是目录、允许判断遍历返回的URL所指项目的最后修改时间、URL目录中所分配的空间大小
*/
NSArray *resourceKeys = @[NSURLIsDirectoryKey, NSURLContentModificationDateKey, NSURLTotalFileAllocatedSizeKey];

// 3.Enumerator(链表的数据结构存在的) 迭代器的设计模式
// 使用目录枚举器获取缓存文件
// 使用NSdirectoryEnumerator遍历所有的缓存文件,获取文件属性,如我们需要的文件大小信息,是不会有性能问题的。NSdirectoryEnumerator获取文件属性是通过查看文件的inode数据,并不需要想象中的fileopen和fileclose。
// 创建目录文件的枚举器
/*
NSDirectoryEnumerationSkipsHiddenFiles 不遍历隐藏文件
NSDirectoryEnumerationSkipsSubdirectoryDescendants 不要下载到目录中
NSDirectoryEnumerationSkipsPackageDescendants 不要下到包
*/
NSDirectoryEnumerator *fileEnumerator = [_fileManager enumeratorAtURL:diskCacheURL
includingPropertiesForKeys:resourceKeys
options:NSDirectoryEnumerationSkipsHiddenFiles
errorHandler:NULL];

// 4.从现在开始,减去最大时间m,计算过期时间,默认1周以前的缓存文件为过期
NSDate *expirationDate = [NSDate dateWithTimeIntervalSinceNow:-self.maxCacheAge];
// 5.定义了一个字典,当前保存下来文件的fileURL
NSMutableDictionary *cacheFiles = [NSMutableDictionary dictionary];
// 6.定义了当前缓存的大小
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.
// 7. 创建一个可变数组,/ 将要删除的文件fileURL数组
NSMutableArray *urlsToDelete = [[NSMutableArray alloc] init];
for (NSURL *fileURL in fileEnumerator) {
// 8.获取指定的key的信息, 以字典的方式返回
NSDictionary *resourceValues = [fileURL resourceValuesForKeys:resourceKeys error:NULL];

// 9. 判断这个目录,有就直接跳过
if ([resourceValues[NSURLIsDirectoryKey] boolValue]) {
continue;
}

// 10.拿到文件的修改日期
NSDate *modificationDate = resourceValues[NSURLContentModificationDateKey];
// 11.记录超过过期日期文件的fileURL
if ([[modificationDate laterDate:expirationDate] isEqualToDate:expirationDate]) {
[urlsToDelete addObject:fileURL];
continue;
}

// 12.保存保留下来的文件的引用并计算文件总的大小
NSNumber *totalAllocatedSize = resourceValues[NSURLTotalFileAllocatedSizeKey];
currentCacheSize += [totalAllocatedSize unsignedIntegerValue];
[cacheFiles setObject:resourceValues forKey:fileURL];
}

// 13. 删除缓存中过期的文件
for (NSURL *fileURL in urlsToDelete) {
[_fileManager removeItemAtURL:fileURL error:nil];
}

// 当设置的maxCacheSize > 0 且 当前缓存中的文件所占内存大于设置的最大缓存时,按时间顺序删除文件,直到缓存剩余空间达阈值的一半
if (self.maxCacheSize > 0 && currentCacheSize > self.maxCacheSize) {
// 14. 清除缓存目标是最大缓存的一半
const NSUInteger desiredCacheSize = self.maxCacheSize / 2;

// 缓存文件排序,最老的文件先清除,NSSortConcurrent 并行的方式
NSArray *sortedFiles = [cacheFiles keysSortedByValueWithOptions:NSSortConcurrent
usingComparator:^NSComparisonResult(id obj1, id obj2) {
return [obj1[NSURLContentModificationDateKey] compare:obj2[NSURLContentModificationDateKey]];
}];

// 遍历删除文件.直到低于缓存大小
for (NSURL *fileURL in sortedFiles) {
if ([_fileManager removeItemAtURL:fileURL error:nil]) {
NSDictionary *resourceValues = cacheFiles[fileURL];
NSNumber *totalAllocatedSize = resourceValues[NSURLTotalFileAllocatedSizeKey];
currentCacheSize -= [totalAllocatedSize unsignedIntegerValue];

if (currentCacheSize < desiredCacheSize) {
break;
}
}
}
}
if (completionBlock) {
dispatch_async(dispatch_get_main_queue(), ^{
completionBlock();
});
}
});
}

SDWebImageDownloader

SDWebImageManager 通过调用调 SDWebImageDownloader 的 downloadImageWithURL: options: progress: completed: 方法发起下载操作, SDWebImageDownloader 实现了图片加载的具体处理,如果图片在缓存存在则从缓存区,如果缓存不存在,则直接创建一个SDWebImageDownloaderOperation对象来下载图片;

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
- (id <SDWebImageOperation>)downloadImageWithURL:(NSURL *)url options:(SDWebImageDownloaderOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageDownloaderCompletedBlock)completedBlock {
// 创建一个 NSOperation 对象
__block SDWebImageDownloaderOperation *operation;
__weak __typeof(self)wself = self;

// 1. 设置 timeout 值为15秒
[self addProgressCallback:progressBlock completedBlock:completedBlock forURL:url createCallback:^{
NSTimeInterval timeoutInterval = wself.downloadTimeout;
if (timeoutInterval == 0.0) {
timeoutInterval = 15.0;
}

// 2. 设置缓存策略
// In order to prevent from potential duplicate caching (NSURLCache + SDImageCache) we disable the cache for image requests if told otherwise
// NSURLRequestUseProtocolCachePolicy 使用网络协议中实现的缓存逻辑,默认的策略, NSURLRequestReloadIgnoringLocalCacheData 数据需要从原始地址加载,不使用现有缓存
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url cachePolicy:(options & SDWebImageDownloaderUseNSURLCache ? NSURLRequestUseProtocolCachePolicy : NSURLRequestReloadIgnoringLocalCacheData) timeoutInterval:timeoutInterval];
// 设置 cookies
request.HTTPShouldHandleCookies = (options & SDWebImageDownloaderHandleCookies);
// 设置管道
request.HTTPShouldUsePipelining = YES;
// 添加自定义请求头
if (wself.headersFilter) {
request.allHTTPHeaderFields = wself.headersFilter(url, [wself.HTTPHeaders copy]);
}
else {
request.allHTTPHeaderFields = wself.HTTPHeaders;
}
// 3. 创建 URLRequest,设置Headers 和认证
// 调起 SDWebImageDownloaderOperation 网络下载图片
operation = [[wself.operationClass alloc] initWithRequest:request
options:options
progress:^(NSInteger receivedSize, NSInteger expectedSize) {
SDWebImageDownloader *sself = wself;
if (!sself) return;
__block NSArray *callbacksForURL;
dispatch_sync(sself.barrierQueue, ^{
callbacksForURL = [sself.URLCallbacks[url] copy];
});
for (NSDictionary *callbacks in callbacksForURL) {
dispatch_async(dispatch_get_main_queue(), ^{
SDWebImageDownloaderProgressBlock callback = callbacks[kProgressCallbackKey];
if (callback) callback(receivedSize, expectedSize);
});
}
}
completed:^(UIImage *image, NSData *data, NSError *error, BOOL finished) {
SDWebImageDownloader *sself = wself;
if (!sself) return;
__block NSArray *callbacksForURL;
dispatch_barrier_sync(sself.barrierQueue, ^{
callbacksForURL = [sself.URLCallbacks[url] copy];
if (finished) {
[sself.URLCallbacks removeObjectForKey:url];
}
});
for (NSDictionary *callbacks in callbacksForURL) {
SDWebImageDownloaderCompletedBlock callback = callbacks[kCompletedCallbackKey];
if (callback) callback(image, data, error, finished);
}
}
cancelled:^{
SDWebImageDownloader *sself = wself;
if (!sself) return;
dispatch_barrier_async(sself.barrierQueue, ^{
[sself.URLCallbacks removeObjectForKey:url];
});
}];
// 是否解压缩返回的图片
operation.shouldDecompressImages = wself.shouldDecompressImages;

// 验证信息
if (wself.urlCredential) {
// SSL验证
operation.credential = wself.urlCredential;
} else if (wself.username && wself.password) {
// 如果设置账户密码验证
operation.credential = [NSURLCredential credentialWithUser:wself.username password:wself.password persistence:NSURLCredentialPersistenceForSession];
}

// 4. operation 设置优先级;
if (options & SDWebImageDownloaderHighPriority) {
operation.queuePriority = NSOperationQueuePriorityHigh;
} else if (options & SDWebImageDownloaderLowPriority) {
operation.queuePriority = NSOperationQueuePriorityLow;
}

// 5. 把 operatin 任务添加进入 NSOperationQueue 队列中
[wself.downloadQueue addOperation:operation];

// 如果是 LIFO 这种模式
if (wself.executionOrder == SDWebImageDownloaderLIFOExecutionOrder) {
// Emulate LIFO execution order by systematically adding new operations as last operation's dependency
// LIFO模式,让前面的 operation 依赖于最新添加的operation
[wself.lastAddedOperation addDependency:operation];
wself.lastAddedOperation = operation;
}
}];

return operation;
}