SDWebImage的图片下载是由SDWebImageDownloader这个类来实现的,它是一个异步下载管理器,下载过程中增加了对图片加载做了优化的处理。而真正实现图片下载的是自定义的一个Operation操作,将该操作加入到下载管理器的操作队列downloadQueue中,Operation操作依赖系统提供的NSURLSession类实现图片的下载。

Asynchronous downloader dedicated and optimized for image loading.
专用的并且优化的图片异步下载器.

我们还是从SDWebImageDownloader的核心方法入手:

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

该方法的大部分代码都放到了一个createCallback的回调中:

1
2
3
4
5
6
7
8
9
10
- (id <SDWebImageOperation>)downloadImageWithURL:(NSURL *)url options:(SDWebImageDownloaderOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageDownloaderCompletedBlock)completedBlock {
__block SDWebImageDownloaderOperation *operation;
__weak __typeof(self)wself = self;
[self addProgressCallback:progressBlock completedBlock:completedBlock forURL:url createCallback:^{
...
}];
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
// The URL will be used as the key to the callbacks dictionary so it cannot be nil. If it is nil immediately call the completed block with no image or data.
if (url == nil) {
if (completedBlock != nil) {
completedBlock(nil, nil, nil, NO);
}
return;
}
dispatch_barrier_sync(self.barrierQueue, ^{
BOOL first = NO;
if (!self.URLCallbacks[url]) {
self.URLCallbacks[url] = [NSMutableArray new];
first = YES;
}
// Handle single download of simultaneous download request for the same URL
NSMutableArray *callbacksForURL = self.URLCallbacks[url];
NSMutableDictionary *callbacks = [NSMutableDictionary new];
if (progressBlock) callbacks[kProgressCallbackKey] = [progressBlock copy];
if (completedBlock) callbacks[kCompletedCallbackKey] = [completedBlock copy];
[callbacksForURL addObject:callbacks];
self.URLCallbacks[url] = callbacksForURL;
if (first) {
createCallback();
}
});

注释有说,这个 URL 参数不能为空,因为它要作为存储 callbacks 字典的 key,如果它为 nil 则会马上调用 completed block 返回 nil 图片和 nil 数据。
self.URLCallbacks 是一个 NSMutableDictionary ,它以 URL 作为 key ,维护一个可变数组 callbacksForURL,这个数组里又会存放一个一个的 NSMutableDictionary 用来存储两个 callback 回调方法,分别是以 kProgressCallbackKey 为 key 的 progressBlock 和 以 kCompletedCallbackKey 为 key 的 completedBlock 。代码里还有一个 BOOL first 的变量,如果发现 self.URLCallbacks 中没有这个 URL 的回调数组,那这个 URL 此时就是第一次请求(此时没有相同的 URL 在请求),会调用 createCallback(); 来创建下载的操作,而发现 self.URLCallbacks 中有这个 URL 的回调数组的话,则将对应的那两个回调方法存进 NSMutableDictionary ,在放到之前的回调数组中,且不会再调用 createCallback(); ,这样相同的 URL 不会重复请求下载。当第一个请求下载成功之后,会遍历这个回调数组,将数组里所有的 callback 都执行一遍。这么做的目的就是防止同时有多个相同 URL 的请求发生。

这段代码使用 dispatch_barrier_sync 将任务放入一个并发队列,目的是在并发队列中,这个任务执行时,不允许别的任务同时执行。因为 downloadImageWithURL: 方法要返回一个遵从<SDWebImageOperation> 的对象,所以要同步执行而不能异步。

1
2
3
4
// SDWebImageDownloader
// downloadImageWithURL:options:progress:completed: #4
[self addProgressCallback:progressBlock andCompletedBlock:completedBlock forURL:url createCallback:^{ ... }];

我们下面来分析这个传入的无参数的代码. 首先这段代码初始化了一个 NSMutableURLRequest:

1
2
3
4
5
6
7
// SDWebImageDownloader
// downloadImageWithURL:options:progress:completed: #11
NSMutableURLRequest *request = [[NSMutableURLRequest alloc]
initWithURL:url
cachePolicy:...
timeoutInterval:timeoutInterval];

这个 request 就用于在之后发送 HTTP 请求.
在初始化了这个 request 之后, 又初始化了一个 SDWebImageDownloaderOperation 的实例, 这个实例, 就是用于请求网络资源的操作. 它是一个 NSOperation 的子类,

1
2
3
4
5
6
7
8
9
// SDWebImageDownloader
// downloadImageWithURL:options:progress:completed: #20
operation = [[SDWebImageDownloaderOperation alloc]
initWithRequest:request
options:options
progress:...
completed:...
cancelled:...}];

progressBlock 的回调方法里,会通过 sself.URLCallbacks 取出这个 URL 所有 kProgressCallbackKey 的回调方法,并将获取到的 receivedSizeexpectedSize 的值传入这些方法中调用。
completedBlock 的回调方法里和 progressBlock 中的一样,取出 kCompletedCallbackKey 对应的回调方法,将获取到的 imagedataerrorfinished 的值传入方法中调用,还会删除 sself.URLCallbacks 中这个 URL 的回调数组 ,保障这个 URL 下次可以重新创建新请求。
cancelBlock 中则只是删除掉 sself.URLCallbacks 中这个 URL 的回调数组。
在往下是,给 operation 设置是否应该解压图片的属性,解压图片会提高下载和缓存的性能,但是会消耗较多的内存,如果程序因为占用内存过多而闪退则要把这个属性设置成 NO。

设置 operation 请求的 NSURLCredential ,用于在请求过程中,服务端要求验证客户端的凭证 - (void)URLSession: task: didReceiveChallenge: completionHandler:
再往后,是设置 NSOperation 的操作优先级。[wself.downloadQueue addOperation:operation]; 是将操作任务加到 NSOperationQueue 队列中,开始任务。最后是设置操作的执行顺序,默认是 FIFO 的先进先出的模式,也可以改成 LIFO 后进先出的栈模式,实现的方法就是添加依赖,前面的操作依赖后面的操作。设置完之后,则 return 这个 operation。
到此,SDWebImageDownloader 的这个核心方法就介绍完了。

还有一点,下载的请求是使用的 NSURLSessionSDWebImageDownloaderNSURLSession 的 delegate 设置成自己,统一接收这些回调方法。在这些回调方法中,会返回一个 NSURLSessionDataTask 通过这个 dataTask 的 taskIdentifier ,我们就可以在 self.downloadQueue.operations 中找到回调方法对应的 operation (SDWebImageDownloaderOperation),每个 operation 中都有这些代理方法,这样在 SDWebImageDownloader 统一接收的回调中用找到的 operation 调用当前的这个代理方法,把参数传到对应的 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
//找到回调方法对应的 operation
- (SDWebImageDownloaderOperation *)operationWithTask:(NSURLSessionTask *)task {
SDWebImageDownloaderOperation *returnOperation = nil;
for (SDWebImageDownloaderOperation *operation in self.downloadQueue.operations) {
if (operation.dataTask.taskIdentifier == task.taskIdentifier) {
returnOperation = operation;
break;
}
}
return returnOperation;
}
#pragma mark NSURLSessionDataDelegate
//调用当前operation的这个代理方法
- (void)URLSession:(NSURLSession *)session
dataTask:(NSURLSessionDataTask *)dataTask
didReceiveResponse:(NSURLResponse *)response
completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler {
// Identify the operation that runs this task and pass it the delegate method
SDWebImageDownloaderOperation *dataOperation = [self operationWithTask:dataTask];
[dataOperation URLSession:session dataTask:dataTask didReceiveResponse:response completionHandler:completionHandler];
}
...

SDWebImageDownloaderOperation- (id)initWithRequest:(NSURLRequest *)request inSession: options: progress: completed: cancelled:; 是下载图片的关键代码,下篇文章我们就来看下SDWebImageDownloaderOperation 这个类。

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