我们利用SDWebImageManager能做很多其他的有意思的事情。比如给各种view绑定一个URL,就能显示图片的功能,有了Options,就能满足多种应用场景的图片下载任务。管理Cache和Downloader等。

目录

  1. SDWebImageManager核心
  2. 其他
    1. SDWenImageManager属性
    2. SDWebImageOptions
    3. SDWenImageManager初始化方法
    4. cacheKeyForURL
    5. 检查图片是否缓存

在 SDWebImageManager.h 中你可以看到关于 SDWebImageManager 的描述:

The SDWebImageManager is the class behind the UIImageView+WebCache category and likes. It ties the asynchronous downloader (SDWebImageDownloader) with the image cache store (SDImageCache). You can use this class directly to benefit from web image downloading with caching in another context than a UIView.

可以看到, 这个类的主要作用就是为 UIImageView+WebCacheSDWebImageDownloader, SDImageCache 之间构建一个桥梁, 使它们能够更好的协同工作, 我们在这里分析这个核心方法的源代码, 它是如何协调异步下载和图片缓存的.

SDWebImageManager核心

我们去看 SDWebImageManager 的核心方法

SDWebImageManager的核心方法
1
2
3
4
- (id <SDWebImageOperation>)downloadImageWithURL:(NSURL *)url
options:(SDWebImageOptions)options
progress:(SDWebImageDownloaderProgressBlock)progressBlock
completed:(SDWebImageCompletionWithFinishedBlock)completedBlock;

我们看这个方法前几句:

1
2
3
4
5
6
7
8
9
10
// Very common mistake is to send the URL using NSString object instead of NSURL. For some strange reason, XCode won't
// throw any warning for this type mismatch. Here we failsafe this error by allowing URLs to be passed as NSString.
if ([url isKindOfClass:NSString.class]) {
url = [NSURL URLWithString:(NSString *)url];
}
// Prevents app crashing on argument type error like sending NSNull instead of NSURL
if (![url isKindOfClass:NSURL.class]) {
url = nil;
}

这块代码的功能是确定 url 是否被正确传入, 如果传入参数的是 NSString 类型就会被转换为 NSURL. 如果转换失败, 那么 url 会被赋值为空, 这个下载的操作就会出错.

1
2
__block SDWebImageCombinedOperation *operation = [SDWebImageCombinedOperation new];
__weak SDWebImageCombinedOperation *weakOperation = operation;

当 url 被正确传入之后, 会实例一个非常奇怪的 “operation”, 它其实是一个遵循 SDWebImageOperation 协议的 NSObject 的子类. 这个类的作用是管理多个模块的取消操作,具体是怎么实现的,后面的代码会提到,__weak 修饰是为了防止循环引用。

下面就是文章最开始提到的功能之一,不可用的 URL 不会一次次重试的功能实现:

1
2
3
4
5
6
7
8
9
10
11
12
BOOL isFailedUrl = NO;
@synchronized (self.failedURLs) {
isFailedUrl = [self.failedURLs containsObject:url];
}
if (url.absoluteString.length == 0 || (!(options & SDWebImageRetryFailed) && isFailedUrl)) {
dispatch_main_sync_safe(^{
NSError *error = [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorFileDoesNotExist userInfo:nil];
completedBlock(nil, error, SDImageCacheTypeNone, YES, url);
});
return operation;
}

downloader 的下载方法的 completedBlock 中会将下载失败的 URL ,维护到 Set 集合中(黑名单),代码会在后面提到。这段代码的意思是如果发现 url 长度为 0 ,或者是下载失败过的 url ,且没有要求重试则直接创建 NSError 并 回调 completedBlock 。

1
2
3
@synchronized (self.runningOperations) {
[self.runningOperations addObject:operation];
}

manager 维护了一个数组 self.runningOperations ,将所有操作放进去,便于管理。(比如统一调用 cancel )下面是比较核心的代码:

1
2
NSString *key = [self cacheKeyForURL:url];
operation.cacheOperation = [self.imageCache queryDiskCacheForKey:key done:^(UIImage *image, SDImageCacheType cacheType) {...}

通过 url 获取用来缓存的 key,尝试去缓存中取图片。queryDiskCacheForKey: done: 方法返回了一个 NSOperation 对象并赋值给了 SDWebImageCombinedOperationcacheOperation ,这个类(SDWebImageCombinedOperation)中还有一个属性 cancelBlock 也会包括一些取消操作。它还实现了协议 <SDWebImageOperation> ,这个协议里只需要实现一个方法,就是 - (void)cancel;

1
2
3
@protocol SDWebImageOperation <NSObject>
- (void)cancel;
@end

SDWebImageCombinedOperation 的实现类中,实现了这个 cancel 方法,并调用了这些取消操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
- (void)cancel {
self.cancelled = YES;
if (self.cacheOperation) {
[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;
}
}

这样,调用者只要调用 operation 的 cancel(),就可以统一对多个模块类做取消操作。

然后看下查询缓存的方法 - (NSOperation *)queryDiskCacheForKey: done: 的实现代码:

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
- (NSOperation *)queryDiskCacheForKey:(NSString *)key
done:(SDWebImageQueryCompletedBlock)doneBlock {
if (!doneBlock) {
return nil;
}
if (!key) {
doneBlock(nil, SDImageCacheTypeNone);
return nil;
}
// 1.首先查看内存缓存,如果查找到,则直接调用doneBlock并返回
UIImage *image = [self imageFromMemoryCacheForKey:key];
if (image) {
doneBlock(image, SDImageCacheTypeMemory);
return nil;
}
//2.如果内存中没有,则在磁盘中查找,如果找到,则将其放到内存缓存中,并调用doneBlock回调
NSOperation *operation = [NSOperation new];
//在ioQueue中串行处理所有磁盘缓存
dispatch_async(self.ioQueue, ^{
if (operation.isCancelled) {
return;
}
//创建自动释放池,内存及时释放
@autoreleasepool {
//根据图片的url对应的key去磁盘缓存中查找图片
UIImage *diskImage = [self diskImageForKey:key];
//如果可以在磁盘中查找到image,并且self.shouldCacheImagesInMemory = YES(默认是YES,if memory cache is enabled)就将image储存到内存缓存中
if (diskImage && self.shouldCacheImagesInMemory) {
NSUInteger cost = SDCacheCostForImage(diskImage);
//self.memCache是NSCache创建的一个对象,下面的方法是NSCache储存对象的方法,如果你对cost的作用不太了解可以看我另外一篇文章NSCache
[self.memCache setObject:diskImage forKey:key cost:cost];
}
//最后在主线程里面调用doneBlock返回
dispatch_async(dispatch_get_main_queue(), ^{
doneBlock(diskImage, SDImageCacheTypeDisk);
});
}
});
return operation;

会先到内存缓存中去查找,如果命中则直接回调,没有命中继续在磁盘缓存中查找。查找任务是异步的,在一个串行队列中,先生成一个 NSOperation 对象返回,也就是赋值给了 operation.cacheOperation 。在查找任务中,会先检测这个 operation 有没有被取消,如果取消则直接 return,这就实现了 SDWebImageCombinedOperation 可以取消查找缓存的操作。之后的代码就是去磁盘缓存中查找图片,且如果需要内存缓存就存进去,最后回调 doneBlock。这段代码被放入到了 @autoreleasepool 中包裹起来,是因为查找出来的图片可能会比较大,占用较多的内存,保障能够及时的回收它。

现在回到 SDWebImageManager 中继续看,在 queryDiskCacheForKey:doneBlock 中,

1
2
3
4
5
6
if (operation.isCancelled) {
@synchronized (self.runningOperations) {
[self.runningOperations removeObject:operation];
}
return;
}

如果操作被取消,则删除掉 self.runningOperations 的操作,然后 return。
接下来会有三个条件分支,我们一个个来看,第一个是:

1
if ((!image || options & SDWebImageRefreshCached) && (![self.delegate respondsToSelector:@selector(imageManager:shouldDownloadImageForURL:)] || [self.delegate imageManager:self shouldDownloadImageForURL:url])) {}

image 为空意味着缓存没有命中,SDWebImageRefreshCached 则是就算缓存命中也要下载图片更新缓存,SDWebImageManager 这个类还定义了一个协议并实现一个代理。

1
2
3
4
5
@protocol SDWebImageManagerDelegate <NSObject>
@optional
- (BOOL)imageManager:(SDWebImageManager *)imageManager shouldDownloadImageForURL:(NSURL *)imageURL;
- (UIImage *)imageManager:(SDWebImageManager *)imageManager transformDownloadedImage:(UIImage *)image withURL:(NSURL *)imageURL;
@end

第一个方法的意思是,是否下载图片,YES 就是下载,NO 就是不下载。第二个方法是,对下载好的图片 image 做 transform 处理,比如可以改成圆角图片等,然后返回,这样缓存的图片也会是 transform 之后的图片。
理解了代理方法的意思就可以理解这个条件了,如果缓存没有命中,或需要刷新已有缓存 且 没有实现 imageManager:shouldDownloadImageForURL 的方法(默认是 YES,可以下载图片)则去下载图片。如果实现了这个代理方法返回的是 YES,也会去下载图片。

看第一个条件里的代码,首先:

1
2
3
4
5
6
7
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);
});
}

缓存如果命中,且需要更新缓存,则先将缓存图片通过 completedBlock 回调出去,在继续下载图片。在往下就是缓存没有命中或需要更新缓存的情况,所以需要下载图片,但之前先将SDWebImageManager 里 option 的条件映射成 SDWebImageDownloder 里的 option 的条件,下载使用的方法是 SDWebImageDownloder 里下面这个方法。

1
- (id <SDWebImageOperation>)downloadImageWithURL:(NSURL *)url options:(SDWebImageDownloaderOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageDownloaderCompletedBlock)completedBlock;

具体实现会在分析 SDWebImageDownloader 源码阅读记录(三) 时会说,我们先看 completedBlock 里的逻辑。

1
2
3
4
5
6
7
__strong __typeof(weakOperation) strongOperation = weakOperation;
if (!strongOperation || strongOperation.isCancelled) {
// Do nothing if the operation was cancelled
//如果操作取消了,不做任何事情
// 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
//如果我们调用completedBlock,这个block会和另外一个completedBlock争夺一个对象,因此这个block被调用后会覆盖新的数据
}

这里注意避免循环引用,imageDownloaderSDWebImageManager 强引用,downloadImageWithURLcompletedBlock 会被 imageDownloader 的属性 URLCallbacks 数组强引用保存起来,至于为什么这么做后面会讲到。

然后是发生错误的处理情况:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
else if (error) {
//进行完成回调
dispatch_main_sync_safe(^{
if (strongOperation && !strongOperation.isCancelled) {
completedBlock(nil, error, SDImageCacheTypeNone, finished, url);
}
});
//将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];
}
}
}

发生错误,并回调。将在确定条件下失败的 URL 放入黑名单,不会反复请求。(通常是 URL 的问题,而不是网络问题)看下 else 之后的代码,稍长一些:

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
else {
//如果设置了下载失败重试,将url从失败列表中去掉
if ((options & SDWebImageRetryFailed)) {
@synchronized (self.failedURLs) {
[self.failedURLs removeObject:url];
}
}
BOOL cacheOnDisk = !(options & SDWebImageCacheMemoryOnly);
//options包含了SDWebImageRefreshCached选项,且缓存中找到了image且没有下载成功
if (options & SDWebImageRefreshCached && image && !downloadedImage) {
// Image refresh hit the NSURLCache cache, do not call the completion block
// 图片刷新遇到了NSSURLCache中有缓存的状况,不调用完成回调。
}
//图片下载成功并且 设置了需要变形Image的选项且变形的代理方法已经实现
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];
if (transformedImage && finished) {
BOOL imageWasTransformed = ![transformedImage isEqual:downloadedImage];
//对已经transform的图片进行缓存
[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);
}
});
});
}
//如果没有图片transform的需求并且图片下载完成且图片存在就直接缓存
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);
}
});
}
}

如果 option 是 SDWebImageRetryFailed 则这个 url 从黑名单中删除,给它一个重试的机会。有 downloadedImage 说明图片下载成功,如果是要进行 transform ,则调用 delegate 方法,获取 transform 之后的图片,进行缓存,再调用 completedBlock 。如果不需要 transform 则直接缓存后回调 completedBlock

1
2
3
4
5
6
7
8
9
10
11
//设置组合操作取消得得回调
operation.cancelBlock = ^{
[subOperation cancel];
@synchronized (self.runningOperations) {
__strong __typeof(weakOperation) strongOperation = weakOperation;
if (strongOperation) {
[self.runningOperations removeObject:strongOperation];
}
}
};

这是下载图片的取消操作,调用 NSOperationcancel,从 self.runningOperations 中删除 operation。赋值给 cancelBlock ,交给 SDWebImageCombinedOperation 对象管理。
看前面提到的三个分支条件的第二个:

1
2
3
4
5
6
7
8
9
10
11
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];
}
}

有 image 说明缓存命中,且没有要重下图片的情况,则直接回调 completedBlock 就可以了。
第三个分支条件,它的意思是既没有缓存图片,代理 delegate 也不允许下载图片,那就只能直接回调 completedBlock ,图片参数传 nil 了。

1
2
3
4
5
6
7
8
9
10
11
12
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];
}
}

到此,SDWebImageManager 的核心方法:

1
2
3
4
- (id <SDWebImageOperation>)downloadImageWithURL:(NSURL *)url
options:(SDWebImageOptions)options
progress:(SDWebImageDownloaderProgressBlock)progressBlock
completed:(SDWebImageCompletionWithFinishedBlock)completedBlock;

就介绍完了。

最后是这个完整核心方法注释

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
operation.cacheOperation = [self.imageCache queryDiskCacheForKey:key done:^(UIImage *image, SDImageCacheType cacheType) {
if (operation.isCancelled) {
@synchronized (self.runningOperations) {
[self.runningOperations removeObject:operation];
}
return;
}
//条件1:在缓存中没有找到图片或者options选项里面包含了SDWebImageRefreshCached(这两项都需要进行请求网络图片的)
//条件2:代理允许下载,SDWebImageManagerDelegate的delegate不能响应imageManager:shouldDownloadImageForURL:方法或者能响应方法且方法返回值为YES.也就是没有实现这个方法就是允许的,如果实现了的话,返回YES才是允许
if ((!image || options & SDWebImageRefreshCached) && (![self.delegate respondsToSelector:@selector(imageManager:shouldDownloadImageForURL:)] || [self.delegate imageManager:self shouldDownloadImageForURL:url])) {
//如果在缓存中找到了image且options选项包含SDWebImageRefreshCached,先在主线程完成一次回调,使用的是缓存中找的图片
if (image && options & SDWebImageRefreshCached) {
dispatch_main_sync_safe(^{
// 如果在缓存中找到了image但是设置了SDWebImageRefreshCached选项,传递缓存的image,同时尝试重新下载它来让NSURLCache有机会接收服务器端的更新
completedBlock(image, nil, cacheType, YES, url);
});
}
// 如果没有在缓存中找到image 或者设置了需要请求服务器刷新的选项,则仍需要下载
SDWebImageDownloaderOptions downloaderOptions = 0;
//开始各种options的判断
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) {
// 如果image已经被缓存但是设置了需要请求服务器刷新的选项,强制关闭渐进式选项
downloaderOptions &= ~SDWebImageDownloaderProgressiveDownload;
// 如果image已经被缓存但是设置了需要请求服务器刷新的选项,忽略从NSURLCache读取的image
downloaderOptions |= SDWebImageDownloaderIgnoreCachedResponse;
}
//创建下载操作,先使用self.imageDownloader下载
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
//如果操作取消了,不做任何事情
// 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
//如果我们调用completedBlock,这个block会和另外一个completedBlock争夺一个对象,因此这个block被调用后会覆盖新的数据
}
else if (error) {
//进行完成回调
dispatch_main_sync_safe(^{
if (strongOperation && !strongOperation.isCancelled) {
completedBlock(nil, error, SDImageCacheTypeNone, finished, url);
}
});
//将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 {
//如果设置了下载失败重试,将url从失败列表中去掉
if ((options & SDWebImageRetryFailed)) {
@synchronized (self.failedURLs) {
[self.failedURLs removeObject:url];
}
}
BOOL cacheOnDisk = !(options & SDWebImageCacheMemoryOnly);
//options包含了SDWebImageRefreshCached选项,且缓存中找到了image且没有下载成功
if (options & SDWebImageRefreshCached && image && !downloadedImage) {
// Image refresh hit the NSURLCache cache, do not call the completion block
// 图片刷新遇到了NSSURLCache中有缓存的状况,不调用完成回调。
}
//图片下载成功并且 设置了需要变形Image的选项且变形的代理方法已经实现
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];
if (transformedImage && finished) {
BOOL imageWasTransformed = ![transformedImage isEqual:downloadedImage];
//对已经transform的图片进行缓存
[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);
}
});
});
}
//如果没有图片transform的需求并且图片下载完成且图片存在就直接缓存
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];
}
}
};
}
//处理其他情况
//case1.在缓存中找到图片(代理不允许下载 或者没有设置SDWebImageRefreshCached选项 满足至少一项)
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];
}
}
//case2:缓存中没有扎到图片且代理不允许下载
else {
//主线程执行完成回调
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];
}
}
}];

其他

SDWenImageManager属性

属性
1
2
3
4
5
6
7
8
9
10
11
12
@interface SDWebImageManager ()
@property (weak, nonatomic) id <SDWebImageManagerDelegate> delegate;
@property (strong, nonatomic, readonly) SDImageCache *imageCache;
@property (strong, nonatomic, readonly) SDWebImageDownloader *imageDownloader;
@property (nonatomic, copy) SDWebImageCacheKeyFilterBlock cacheKeyFilter;
@end
/*
概述了SDWenImageManager的作用,其实UIImageVIew+WebCache这个Category背后执行操作的就是这个SDWebImageManager.它会绑定一个下载器也就是SDwebImageDownloader和一个缓存SDImageCache
*/
/*若图片不在cache中,就根据给定的URL下载图片,否则返回cache中的图片 */

SDWebImageOptions

SDWebImageOptions作为下载的选项提供了非常多的子项,用法和注意事项我都写在代码的注释中了:

SDWebImageOptions
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
typedef NS_OPTIONS(NSUInteger, SDWebImageOptions) {
/**
* By default, when a URL fail to be downloaded, the URL is blacklisted so the library won't keep trying.
* This flag disable this blacklisting.
*/
/// 每一个下载都会提供一个URL,如果这个URL是错误,SD就会把它放入到黑名单之中,
/// 黑名单中的URL是不会再次进行下载的,但是,当设置了该选项时,SD会将其在黑名单中移除,重新下载该URL,
SDWebImageRetryFailed = 1 << 0,
/**
* By default, image downloads are started during UI interactions, this flags disable this feature,
* leading to delayed download on UIScrollView deceleration for instance.
*/
/// 一般来说,下载都是按照一定的先后顺序开始的,但是该选项能够延迟下载,也就说他的权限比较低,权限比他高的在他前边下载
SDWebImageLowPriority = 1 << 1,
/**
* This flag disables on-disk caching
*/
/// 该选项要求SD只把图片缓存到内存中,不缓存到disk中
SDWebImageCacheMemoryOnly = 1 << 2,
/**
* This flag enables progressive download, the image is displayed progressively during download as a browser would do.
* By default, the image is only displayed once completely downloaded.
*/
/// 给下载添加进度
SDWebImageProgressiveDownload = 1 << 3,
/**
* Even if the image is cached, respect the HTTP response cache control, and refresh the image from remote location if needed.
* The disk caching will be handled by NSURLCache instead of SDWebImage leading to slight performance degradation.
* This option helps deal with images changing behind the same request URL, e.g. Facebook graph api profile pics.
* If a cached image is refreshed, the completion block is called once with the cached image and again with the final image.
*
* Use this flag only if you can't make your URLs static with embedded cache busting parameter.
*/
/// 有这么一种使用场景,如果一个图片的资源发生了改变。但是url并没有变,我们就可以使用该选项来刷新数据了
SDWebImageRefreshCached = 1 << 4,
/**
* In iOS 4+, continue the download of the image if the app goes to background. This is achieved by asking the system for
* extra time in background to let the request finish. If the background task expires the operation will be cancelled.
*/
/// 支持切换到后台也能下载
SDWebImageContinueInBackground = 1 << 5,
/**
* Handles cookies stored in NSHTTPCookieStore by setting
* NSMutableURLRequest.HTTPShouldHandleCookies = YES;
*/
/// 使用Cookies
SDWebImageHandleCookies = 1 << 6,
/**
* Enable to allow untrusted SSL certificates.
* Useful for testing purposes. Use with caution in production.
*/
/// 允许验证证书
SDWebImageAllowInvalidSSLCertificates = 1 << 7,
/**
* By default, images are loaded in the order in which they were queued. This flag moves them to
* the front of the queue.
*/
/// 高权限
SDWebImageHighPriority = 1 << 8,
/**
* By default, placeholder images are loaded while the image is loading. This flag will delay the loading
* of the placeholder image until after the image has finished loading.
*/
/// 一般情况下,placeholder image 都会在图片下载完成前显示,该选项将设置placeholder image在下载完成之后才能显示
SDWebImageDelayPlaceholder = 1 << 9,
/**
* We usually don't call transformDownloadedImage delegate method on animated images,
* as most transformation code would mangle it.
* Use this flag to transform them anyway.
*/
/// 使用该属性来自由改变图片,但需要使用transformDownloadedImage delegate
SDWebImageTransformAnimatedImage = 1 << 10,
/**
* By default, image is added to the imageView after download. But in some cases, we want to
* have the hand before setting the image (apply a filter or add it with cross-fade animation for instance)
* Use this flag if you want to manually set the image in the completion when success
*/
/// 该选项允许我们在图片下载完成后不会立刻给view设置图片,比较常用的使用场景是给赋值的图片添加动画
SDWebImageAvoidAutoSetImage = 1 << 11,
/**
* By default, images are decoded respecting their original size. On iOS, this flag will scale down the
* images to a size compatible with the constrained memory of devices.
* If `SDWebImageProgressiveDownload` flag is set the scale down is deactivated.
*/
/// 压缩大图片
SDWebImageScaleDownLargeImages = 1 << 12
};

SDWenImageManager初始化方法

初始化方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/*
*初始化方法
*1.获得一个SDImageCache的单例
*2.获得一个SDWebImageDownloader的单例
*3.新建一个MutableSet来存储下载失败的url
*4.新建一个用来存储下载operation的可遍数组
*/
- (instancetype)initWithCache:(SDImageCache *)cache downloader:(SDWebImageDownloader *)downloader {
if ((self = [super init])) {
_imageCache = cache;
_imageDownloader = downloader;
_failedURLs = [NSMutableSet new];
_runningOperations = [NSMutableArray new];
}
return self;
}

cacheKeyForURL

1
2
3
4
5
6
7
8
9
10
//如果检测到cacheKeyFilter不为空的时候,利用cacheKeyFilter来生成一个key
//如果为空,那么直接返回URL的string内容,当做key.
- (NSString *)cacheKeyForURL:(NSURL *)url {
if (self.cacheKeyFilter) {
return self.cacheKeyFilter(url);
}
else {
return [url absoluteString];
}
}

检查图片是否缓存

1
2
3
4
5
6
7
8
9
10
11
12
13
14
- (BOOL)cachedImageExistsForURL:(NSURL *)url {
//调用上面的方法取到image的url对应的key
NSString *key = [self cacheKeyForURL:url];
//首先检测内存缓存中时候存在这张图片,如果已有直接返回yes
if ([self.imageCache imageFromMemoryCacheForKey:key] != nil) return YES;
//如果内存缓存里面没有这张图片,那么就调用diskImageExistsWithKey这个方法去硬盘找
return [self.imageCache diskImageExistsWithKey:key];
}
// 检测硬盘里是否缓存了图片
- (BOOL)diskImageExistsForURL:(NSURL *)url {
NSString *key = [self cacheKeyForURL:url];
return [self.imageCache diskImageExistsWithKey:key];
}

下面两个方法比较类似,都是先根据图片的url创建对应的key第一个方法先用BOOL isInMemoryCache = ([self.imageCache imageFromMemoryCacheForKey:key] != nil);判断图片有没有在内存缓存中,如果图片在内存缓存中存在,就在主线程里面回调block,如果图片没有在内存缓存中就去查找是不是在磁盘缓存里面,然后在主线程里面回到block,第二个方法只查询图片是否在磁盘缓存里面,然后在主线程里面回调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
- (void)cachedImageExistsForURL:(NSURL *)url
completion:(SDWebImageCheckCacheCompletionBlock)completionBlock {
NSString *key = [self cacheKeyForURL:url];
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);
}
}];
}
- (void)diskImageExistsForURL:(NSURL *)url
completion:(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);
}
}];
}

相关阅读:
SDWebImage 源码阅读记录(一)
SDWebImageManager 源码阅读记录(二)
SDWebImageDownloader 源码阅读记录(三)
SDWebImageDownloaderOperation 源码阅读记录(四)
SDWebImageCache 源码阅读记录(五)
SDWebImage相关类 源码阅读记录(六)