本文接 阅读 SDWebImage 源码(二) ,本篇阅读 SDWebImageDownloaderOperation 里面的源码。

它主要负责图片的下载操作。在 SDWebImage 源码(一) 介绍中,它在 SDWebImageDownloader 里使用。

SDWebImageDownloaderOperation,是 NSOperation 的子类,并发操作。结合 NSURLConnection 进行网络请求。

自定义并发操作

  • 重写 start 方法
  • isConcurrent 是否并发。返回 YES
  • isExecuting 是否正在执行 KVO
  • isFinish 是否完成 KVO

SDWebImageDownloaderOperation 就是很好的 Demo。

Start 方法

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
// SDWebImageDownloaderOperation.m
- (void)start {
@synchronized (self) {
if (self.isCancelled) {
self.finished = YES;
[self reset];
return;
}
// 0
#if TARGET_OS_IPHONE && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_4_0
Class UIApplicationClass = NSClassFromString(@"UIApplication");
BOOL hasApplication = UIApplicationClass && [UIApplicationClass respondsToSelector:@selector(sharedApplication)];
if (hasApplication && [self shouldContinueWhenAppEntersBackground]) {
__weak __typeof__ (self) wself = self;
UIApplication * app = [UIApplicationClass performSelector:@selector(sharedApplication)];
self.backgroundTaskId = [app beginBackgroundTaskWithExpirationHandler:^{
__strong __typeof (wself) sself = wself;
if (sself) {
[sself cancel];
[app endBackgroundTask:sself.backgroundTaskId];
sself.backgroundTaskId = UIBackgroundTaskInvalid;
}
}];
}
#endif
self.executing = YES;
self.connection = [[NSURLConnection alloc] initWithRequest:self.request delegate:self startImmediately:NO]; // 1
self.thread = [NSThread currentThread];
}
[self.connection start];
if (self.connection) {
if (self.progressBlock) {
self.progressBlock(0, NSURLResponseUnknownLength);
}
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStartNotification object:self];
});
// 2
if (floor(NSFoundationVersionNumber) <= NSFoundationVersionNumber_iOS_5_1) {
// Make sure to run the runloop in our background thread so it can process downloaded data
// Note: we use a timeout to work around an issue with NSURLConnection cancel under iOS 5
// not waking up the runloop, leading to dead threads (see https://github.com/rs/SDWebImage/issues/466)
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 10, false);
}
else {
CFRunLoopRun();
}
if (!self.isFinished) {
[self.connection cancel];
[self connection:self.connection didFailWithError:[NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorTimedOut userInfo:@{NSURLErrorFailingURLErrorKey : self.request.URL}]];
}
}
else {
if (self.completedBlock) {
self.completedBlock(nil, nil, [NSError errorWithDomain:NSURLErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey : @"Connection can't be initialized"}], YES);
}
}
// 3
#if TARGET_OS_IPHONE && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_4_0
Class UIApplicationClass = NSClassFromString(@"UIApplication");
if(!UIApplicationClass || ![UIApplicationClass respondsToSelector:@selector(sharedApplication)]) {
return;
}
if (self.backgroundTaskId != UIBackgroundTaskInvalid) {
UIApplication * app = [UIApplication performSelector:@selector(sharedApplication)];
[app endBackgroundTask:self.backgroundTaskId];
self.backgroundTaskId = UIBackgroundTaskInvalid;
}
#endif
}

0.开启后台下载

1.使用 NSURLConnection 进行网络请求

2.开启 Run Loop。使 NSURLConnection 正常使用

3.关闭后台下载

其它

关于 NSURLConnectionDataDelegate 具体使用,这里不再陈述。列几点其他代码:

  • - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response 方法中,通过 [((NSHTTPURLResponse *)response) statusCode] 获取 HTTP 状态码。对于常见的 HTTP 状态码,我们需要熟知。这里状态码 304 是指图片没有被修改。

  • SDWebImage 都是将图片解码过程放在子线程中进行处理。不论是前面的 ioQueue 还是这里的 Operation

  • 最后由 SDWebImageDownloaderOperation 创建的 operation 放到了
    SDWebImageDownloader 队列里,这个队列默认最大并发量为 6