读 SDWebImage 四 (SDWebImageDownloader)
2018-10-04 19:38:50 # SDWebImage

SDWebImage 的图片下载是由 SDWebImageDownloader 这个类实现的,该类是一个异步下载管理器。主要工作是下载相关配置项的管理,包括下载队列的先后顺序、最大下载任务数量控制、下载队列中的任务创建、取消、暂停等任务管理,以及其他的 HTTPSHTTP Header 的设置。而真正实现图片下载的是 SDWebImageDownloaderOperation类。该类的 Operation 操作依赖系统提供的NSURLConnection类来实现图片的下载。

下载选项

枚举列出不同的下载选项 (选项使用掩码形式,如 1 << 2 表示将1左移2位,即:00000010,也就是2。)

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
typedef NS_OPTIONS(NSUInteger, SDWebImageDownloaderOptions) {

//默认模式,将下载放入低优先级队列 和 低优先级任务中
SDWebImageDownloaderLowPriority = 1 << 0,

/**
* 该选项启用渐进式下载,图像在下载过程中逐步显示,就像浏览器一样。(该选项在返回进度Block的同时也返回completedBlock,里面的UIImage就是当前下载时的图片,可以实现将图片一点点显示出来的功能)
*/
SDWebImageDownloaderProgressiveDownload = 1 << 1,

/**
* 默认情况下,http请求会阻止使用NSURLCache。 使用此标志,NSURLCache将与默认策略一起使用。
*/
SDWebImageDownloaderUseNSURLCache = 1 << 2,

/**
* 如果从NSURLCache缓存中读取图片,则使用nil作为image/imageData的参数来调用block代码块。
*/
SDWebImageDownloaderIgnoreCachedResponse = 1 << 3,

/**
* 在iOS 4+ 系统中,允许程序进入后台后继续下载图片,该操作通过向系统申请额外的时间来完成后台下载请求,如果后台任务终止,则操作被取消。
*/
SDWebImageDownloaderContinueInBackground = 1 << 4,

/**
* 通过设置NSMutableURLRequest,HTTPShouldHandleCookies = YES来处理存储在NSHTTPCookieStore中的cookie;
*/
SDWebImageDownloaderHandleCookies = 1 << 5,

/**
*允许允许不受信任的SSL证书。用于测试目的。 在生产中谨慎使用。
*/
SDWebImageDownloaderAllowInvalidSSLCertificates = 1 << 6,

/**
* 将下载放入高队列优先级和高任务优先级中。
*/
SDWebImageDownloaderHighPriority = 1 << 7,

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

下载操作的执行顺序

两种执行顺序: 先进先出 和 后进先出

1
2
3
4
5
6
7
8
9
10
11
typedef NS_ENUM(NSInteger, SDWebImageDownloaderExecutionOrder) {
/**
* 默认值。 所有下载操作都将以队列样式执行(先进先出)。
*/
SDWebImageDownloaderFIFOExecutionOrder,

/**
* 所有下载操作都将以栈的方式执行(后进先出)。
*/
SDWebImageDownloaderLIFOExecutionOrder
};

外部定义的系统通知标示

1
2
3
4
//开始下载通知
FOUNDATION_EXPORT NSString * _Nonnull const SDWebImageDownloadStartNotification;
//停止下载通知
FOUNDATION_EXPORT NSString * _Nonnull const SDWebImageDownloadStopNotification;

两个代码块

每个下载操作的下载进度回调和下载完成回调,这两个回调稍后将保存在下载管理器的URLCallbacks字典中,key为URL,value为一个数组,数组里面又存放一个保存了下载进度回调和完成回调代码块的字典。

1
2
3
4
//下载进度代码块
typedef void(^SDWebImageDownloaderProgressBlock)(NSInteger receivedSize, NSInteger expectedSize, NSURL * _Nullable targetURL);
//下载完成回调代码块
typedef void(^SDWebImageDownloaderCompletedBlock)(UIImage * _Nullable image, NSData * _Nullable data, NSError * _Nullable error, BOOL finished);

三个字典

1
2
3
4
5
6
//请求头字典
typedef NSDictionary<NSString *, NSString *> SDHTTPHeadersDictionary;
typedef NSMutableDictionary<NSString *, NSString *> SDHTTPHeadersMutableDictionary;

//自定义请求头,通过Block传值,可以拿到一些参数,然后加工成我们需要的数据,最后返回
typedef SDHTTPHeadersDictionary * _Nullable (^SDWebImageDownloaderHeadersFilterBlock)(NSURL * _Nullable url, SDHTTPHeadersDictionary * _Nullable headers);

SDWebImageDownloadToken

作为下载操作的唯一标识,在创建 operation 的时候初始化绑定,当需要去 cancel 操作的时候就需要这个 token
SDWebImageDownloadToken 为每一个下载任务的唯一身份标识,SDWebImageDownloader 和我们平时开发中的下载有一些不同,它弱化了下载过程,比较强调的是下载结果,不支持断点下载。

.h文件

1
2
3
4
5
6
7
8
9
10
11
12
13
@interface SDWebImageDownloadToken : NSObject <SDWebImageOperation>

/**
下载的URL。 这应该是只读的,你不应该修改
*/
@property (nonatomic, strong, nullable) NSURL *url;
/**
取消token 可以从 “id downloadOperationCancelToken = [operation addHandlersForProgress:progressBlock completed:completedBlock]” 方法获得,这应该是只读的,你不应该修改。
@note 使用 `-[SDWebImageDownloadToken cancel]`方法去取消下载token
*/
@property (nonatomic, strong, nullable) id downloadOperationCancelToken;

@end

.m文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@interface SDWebImageDownloadToken ()

//下载操作
@property (nonatomic, weak, nullable) NSOperation<SDWebImageDownloaderOperationInterface> *downloadOperation;

@end

@implementation SDWebImageDownloadToken

//取消下载
- (void)cancel {
if (self.downloadOperation) {
SDWebImageDownloadToken *cancelToken = self.downloadOperationCancelToken;
if (cancelToken) {
[self.downloadOperation cancel:cancelToken];
}
}
}

@end

SDWebImageDownloader.h文件

属性声明

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
/**
* 解压缩下载和缓存的图像可以提高性能,但会占用大量内存。
* 默认是YES. 如果由于过多的内存消耗而遇到崩溃,可以设置为NO
*/
//当图片下载完成以后,解码图片。如果因为过多的内存消耗导致一个奔溃,可以把这个属性设置为NO
@property (assign, nonatomic) BOOL shouldDecompressImages;

/**
* 最大兵法下载数目
*/
@property (assign, nonatomic) NSInteger maxConcurrentDownloads;

/**
* 显示仍需要下载的当前下载量
*/
@property (readonly, nonatomic) NSUInteger currentDownloadCount;

/**
* 下载操作的超时值(秒为单位),默认15秒
*/
@property (assign, nonatomic) NSTimeInterval downloadTimeout;

/**
* 内部NSURLSession使用的配置
* 直接变换此对象无效
*
* @see createNewSessionWithConfiguration:
*/
@property (readonly, nonatomic, nonnull) NSURLSessionConfiguration *sessionConfiguration;

/**
* 改变下载操作的执行顺序 默认值是 `SDWebImageDownloaderFIFOExecutionOrder`.
*/
@property (assign, nonatomic) SDWebImageDownloaderExecutionOrder executionOrder;

/**
* 设置要为请求操作设置的默认URL凭据。
*/
@property (strong, nonatomic, nullable) NSURLCredential *urlCredential;

/**
* 设置用户名
*/
@property (strong, nonatomic, nullable) NSString *username;

/**
* 设置密码
*/
@property (strong, nonatomic, nullable) NSString *password;

/**
* 设置过滤器以选择用于下载图像HTTP请求的标头
*
* 将为每个下载图像请求调用此块,返回的NSDictionary将用作相应HTTP请求中的标头。
*/
//http头部的过滤函数
@property (nonatomic, copy, nullable) SDWebImageDownloaderHeadersFilterBlock headersFilter;

方法

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
//下载管理器单例函数
+ (nonnull instancetype)sharedDownloader;

/**
* 使用指定的会话配置创建下载程序的实例。(初始化方法)
* @note `timeoutIntervalForRequest` 将被覆盖
* @return 下载器的新事例
*/
- (nonnull instancetype)initWithSessionConfiguration:(nullable NSURLSessionConfiguration *)sessionConfiguration NS_DESIGNATED_INITIALIZER;

/**
* 设置要附加到每个下载HTTP请求的HTTP标头的值。
*
* @param value The value for the header field. 值为 `nil` 移除请求头文件.
* @param field 设置http请求头部字段.
*/
- (void)setValue:(nullable NSString *)value forHTTPHeaderField:(nullable NSString *)field;

/**
* 返回指定的HTTP标头字段的值。
*
* @return 与标题字段字段关联的值,如果没有相应的标题字段,则为“nil”。
*/
- (nullable NSString *)valueForHTTPHeaderField:(nullable NSString *)field;

/**
* 将`SDWebImageDownloaderOperation`的子类设置为默认值
* 每次SDWebImage构造请求时都要使用`NSOperation`
* 下载图像的操作。
*
* @param operationClass要设置的`SDWebImageDownloaderOperation`的子类
*默认。 传递`nil`将恢复为`SDWebImageDownloaderOperation`。
*/
- (void)setOperationClass:(nullable Class)operationClass;

/ **
* 使用给定的URL创建SDWebImageDownloader异步下载器实例
*
* 图像完成下载或发生错误时将通知代理。
*
* @see SDWebImageDownloaderDelegate
*
* @param url要下载的图像的URL
* @param options用于此下载的选项
* @param progressBlock在下载图像时重复调用的块
* @note在后台队列上执行进度块
* @param completedBlock下载完成后调用的块。
* 如果下载成功,则设置image参数,如果出错,
* 错误参数设置为错误。最后一个参数始终为YES
* 如果没有使用SDWebImageDownloaderProgressiveDownload。随着
* SDWebImageDownloaderProgressiveDownload选项,调用此块
* 重复使用部分图像对象,并将完成的参数设置为NO
* 之前用完整的图像和完成的参数调用最后一次
* 设为是。如果出错,则完成的参数始终为YES。
*
* @return可以传递给-cancel的令牌(SDWebImageDownloadToken):取消此操作
* /
- (nullable SDWebImageDownloadToken *)downloadImageWithURL:(nullable NSURL *)url
options:(SDWebImageDownloaderOptions)options
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock;

/**
* 取消之前使用排队的下载 获取token使用 “-downloadImageWithURL:options:progress:completed:”方法
*
* @param token 从“-downloadImageWithURL:options:progress:completed:”方法获取的token应该被取消
*/
- (void)cancel:(nullable SDWebImageDownloadToken *)token;

/**
* 设置下载队列挂起状态
*/
- (void)setSuspended:(BOOL)suspended;

/**
* 取消队列中的所有下载操作
*/
- (void)cancelAllDownloads;

/**
* 强制SDWebImageDownloader 创建和使用一个给定配置的初始化NSURLSession(队列中的所有现有下载操作都将被取消;请求超时的时间也被重写)
*
* @param sessionConfiguration 使用新的NSURLSession配置
*/
- (void)createNewSessionWithConfiguration:(nonnull NSURLSessionConfiguration *)sessionConfiguration;

/**
* //取消operation并且session设置为Invalidates (如果您使用自定义下载程序而不是共享下载程序,则在不使用它时避免内存泄漏时需要调用此方法)
* @param cancelPendingOperations 是否取消挂起的操作。
* @note 在共享下载程序上调用此方法无效。
*/
- (void)invalidateSessionAndCancel:(BOOL)cancelPendingOperations;

SDWebImageDownloader.m文件

属性声明

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@interface SDWebImageDownloader () <NSURLSessionTaskDelegate, NSURLSessionDataDelegate>

//下载队列
@property (strong, nonatomic, nonnull) NSOperationQueue *downloadQueue;
//最新添加的操作
@property (weak, nonatomic, nullable) NSOperation *lastAddedOperation;
//操作的类
@property (assign, nonatomic, nullable) Class operationClass;
//下载操作的集合
@property (strong, nonatomic, nonnull) NSMutableDictionary<NSURL *, SDWebImageDownloaderOperation *> *URLOperations;
//HTTP头文件集合
@property (strong, nonatomic, nullable) SDHTTPHeadersMutableDictionary *HTTPHeaders;
//操作锁
@property (strong, nonatomic, nonnull) dispatch_semaphore_t operationsLock; // a lock to keep the access to `URLOperations` thread-safe
//头文件锁
@property (strong, nonatomic, nonnull) dispatch_semaphore_t headersLock; // a lock to keep the access to `HTTPHeaders` thread-safe

// The session in which data tasks will run
//NSURLSession
@property (strong, nonatomic) NSURLSession *session;

@end

初始化方法

SDWebImageDownloader 提供了一个initialize方法 四个初始化方法和一个注销方法

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
+ (void)initialize {
//绑定SDNetworkActivityIndicator(如果可用),如果使用它,需要导入"SDNetworkActivityIndicator.h"头文件
//主要是用来加载图片的时候,状态栏会转小菊花。(该方法是为了给图片下载绑定一个SDNetworkActivityIndicator,只有当这个SDNetworkActivityIndicator文件存在的情况下才会执行,目的就是当下载图片时,状态栏会转小菊花。)
if (NSClassFromString(@"SDNetworkActivityIndicator")) {

//LLVM 3.0 编译器可以用以下代码消除 warning
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
id activityIndicator = [NSClassFromString(@"SDNetworkActivityIndicator") performSelector:NSSelectorFromString(@"sharedActivityIndicator")];
#pragma clang diagnostic pop

// 如果之前已经添加,先移除观察者
[[NSNotificationCenter defaultCenter] removeObserver:activityIndicator name:SDWebImageDownloadStartNotification object:nil];
[[NSNotificationCenter defaultCenter] removeObserver:activityIndicator name:SDWebImageDownloadStopNotification object:nil];

//添加观察者
[[NSNotificationCenter defaultCenter] addObserver:activityIndicator
selector:NSSelectorFromString(@"startActivity")
name:SDWebImageDownloadStartNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:activityIndicator
selector:NSSelectorFromString(@"stopActivity")
name:SDWebImageDownloadStopNotification object:nil];
}
}

//单例,返回SDWebImageDownloader对象
+ (nonnull instancetype)sharedDownloader {
static dispatch_once_t once;
static id instance;
dispatch_once(&once, ^{
instance = [self new];
});
return instance;
}

- (nonnull instancetype)init {
return [self initWithSessionConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]];
}

//初始化一个请求对象
- (nonnull instancetype)initWithSessionConfiguration:(nullable NSURLSessionConfiguration *)sessionConfiguration {
if ((self = [super init])) {
_operationClass = [SDWebImageDownloaderOperation class];
_shouldDecompressImages = YES;
_executionOrder = SDWebImageDownloaderFIFOExecutionOrder;
_downloadQueue = [NSOperationQueue new];
_downloadQueue.maxConcurrentOperationCount = 6;
_downloadQueue.name = @"com.hackemist.SDWebImageDownloader";
_URLOperations = [NSMutableDictionary new];
#ifdef SD_WEBP
_HTTPHeaders = [@{@"Accept": @"image/webp,image/*;q=0.8"} mutableCopy];
#else
_HTTPHeaders = [@{@"Accept": @"image/*;q=0.8"} mutableCopy];
#endif
_operationsLock = dispatch_semaphore_create(1);
_headersLock = dispatch_semaphore_create(1);
_downloadTimeout = 15.0;

[self createNewSessionWithConfiguration:sessionConfiguration];
}
return self;
}

//初始化一个新的请求对象
- (void)createNewSessionWithConfiguration:(NSURLSessionConfiguration *)sessionConfiguration {
[self cancelAllDownloads];

if (self.session) {
[self.session invalidateAndCancel];
}

sessionConfiguration.timeoutIntervalForRequest = self.downloadTimeout;

/**
* Create the session for this task
* We send nil as delegate queue so that the session creates a serial operation queue for performing all delegate
* method calls and completion handler calls.
*/
self.session = [NSURLSession sessionWithConfiguration:sessionConfiguration
delegate:self
delegateQueue:nil];
}

///取消operation并且session设置为Invalidates
- (void)invalidateSessionAndCancel:(BOOL)cancelPendingOperations {
if (self == [SDWebImageDownloader sharedDownloader]) {
return;
}
if (cancelPendingOperations) {
[self.session invalidateAndCancel];
} else {
[self.session finishTasksAndInvalidate];
}
}

- (void)dealloc {
[self.session invalidateAndCancel];
self.session = nil;

[self.downloadQueue cancelAllOperations];
}

SetGet 方法

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
//设置请求报文头部
- (void)setValue:(nullable NSString *)value forHTTPHeaderField:(nullable NSString *)field {
LOCK(self.headersLock);
if (value) {
self.HTTPHeaders[field] = value;
} else {
[self.HTTPHeaders removeObjectForKey:field];
}
UNLOCK(self.headersLock);
}

//获取请求报文头部
- (nullable NSString *)valueForHTTPHeaderField:(nullable NSString *)field {
if (!field) {
return nil;
}
return [[self allHTTPHeaderFields] objectForKey:field];
}

//全部的请求报文头部
- (nonnull SDHTTPHeadersDictionary *)allHTTPHeaderFields {
LOCK(self.headersLock);
SDHTTPHeadersDictionary *allHTTPHeaderFields = [self.HTTPHeaders copy];
UNLOCK(self.headersLock);
return allHTTPHeaderFields;
}

//设置下载队列的最大并发数
- (void)setMaxConcurrentDownloads:(NSInteger)maxConcurrentDownloads {
_downloadQueue.maxConcurrentOperationCount = maxConcurrentDownloads;
}

//获取下载队列的当前operation数
- (NSUInteger)currentDownloadCount {
return _downloadQueue.operationCount;
}

//获取下载队列的最大并发数
- (NSInteger)maxConcurrentDownloads {
return _downloadQueue.maxConcurrentOperationCount;
}

- (NSURLSessionConfiguration *)sessionConfiguration {
return self.session.configuration;
}

// 设置一个`SDWebImageDownloaderOperation`的子类作为`NSOperation`来构建request来下载一张图片
- (void)setOperationClass:(nullable Class)operationClass {
if (operationClass && [operationClass isSubclassOfClass:[NSOperation class]] && [operationClass conformsToProtocol:@protocol(SDWebImageDownloaderOperationInterface)]) {
_operationClass = operationClass;
} else {
_operationClass = [SDWebImageDownloaderOperation class];
}
}

下载方法

下载管理器的主要实现为 downloadImageWithURL:options:progress:completed: 方法(调用该方法创建 operation 操作),这个方法调用 - (void)addProgressCallback:completedBlock:forURL: createCallback: 方法来将请求的信息和一些回调函数存入管理器中,同时在创建回调的 block 中创建新 operation 操作,新的操作由管理器中存储的信息配置后,放入到 downloadQueue 操作队列中,最后返回新创建的操作

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
/**
下载图片
@param url 图片url
@param options 下载选项
@param progressBlock 下载进度blockhui
@param completedBlock 下载完成block
@return 返回一个SDWebImageDownloadToken,用于关联一个请求
调用addProgressCallback方法 return token,addProgressCallback的回调里进行以下操作
{
1.1设置下载时间
1.2创建request
1.3创建operation对象 传入 request session options
1.4设置身份认证
1.5设置下载优先级
1.6设置下载顺序
}

*/
- (nullable SDWebImageDownloadToken *)downloadImageWithURL:(nullable NSURL *)url
options:(SDWebImageDownloaderOptions)options
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock {
__weak SDWebImageDownloader *wself = self;
// 调用- (void)addProgressCallback:completedBlock:forURL: createCallback:方法来将请求的信息和一些回调函数存入管理器中,同时在创建回调的block中创建新operation操作,新的操作由管理器中存储的信息配置后,放入到downloadQueue操作队列中,最后返回新创建的操作
return [self addProgressCallback:progressBlock completedBlock:completedBlock forURL:url createCallback:^SDWebImageDownloaderOperation *{
__strong __typeof (wself) sself = wself;
//获取超时时间,如果没有设置,默认为15秒
NSTimeInterval timeoutInterval = sself.downloadTimeout;
if (timeoutInterval == 0.0) {
timeoutInterval = 15.0;
}

// 为了防止潜在的重复缓存(NSURLCache 和 SDImageCache同时缓存),如果另有说明,我们会禁用图像请求的缓存
NSURLRequestCachePolicy cachePolicy = options & SDWebImageDownloaderUseNSURLCache ? NSURLRequestUseProtocolCachePolicy : NSURLRequestReloadIgnoringLocalCacheData;

//创建请求对象,并根据options参数设置其属性
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url
cachePolicy:cachePolicy
timeoutInterval:timeoutInterval];

//使用cookies
request.HTTPShouldHandleCookies = (options & SDWebImageDownloaderHandleCookies);
//使用管道
request.HTTPShouldUsePipelining = YES;
//添加自定义请求头
if (sself.headersFilter) {
request.allHTTPHeaderFields = sself.headersFilter(url, [sself allHTTPHeaderFields]);
}
else {
request.allHTTPHeaderFields = [sself allHTTPHeaderFields];
}
//初始化一个自定义NSOperation对象
SDWebImageDownloaderOperation *operation = [[sself.operationClass alloc] initWithRequest:request inSession:sself.session options:options];
//当图片下载完成以后,解码图片。如果因为过多的内存消耗导致一个奔溃,可以把这个属性设置为NO
operation.shouldDecompressImages = sself.shouldDecompressImages;

//指定验证信息(url证书)
if (sself.urlCredential) {
operation.credential = sself.urlCredential;
} else if (sself.username && sself.password) {
//基础验证
operation.credential = [NSURLCredential credentialWithUser:sself.username password:sself.password persistence:NSURLCredentialPersistenceForSession];
}

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

//指定下载顺序( 如果是LIFO这种模式,则需要手动指定operation之间的依赖关系)
if (sself.executionOrder == SDWebImageDownloaderLIFOExecutionOrder) {
// 如果是LIFO,则让前面的operation依赖于最新添加的operation
[sself.lastAddedOperation addDependency:operation];
sself.lastAddedOperation = operation;
}

return operation;
}];
}

//取消一个图片的下载操作
- (void)cancel:(nullable SDWebImageDownloadToken *)token {
//如果url不存在,则返回
NSURL *url = token.url;
if (!url) {
return;
}
//获取该操作,通过token来确定操作是否取消(移除),如果取消,则在URLOperations中移除url
LOCK(self.operationsLock);
SDWebImageDownloaderOperation *operation = [self.URLOperations objectForKey:url];
if (operation) {
BOOL canceled = [operation cancel:token.downloadOperationCancelToken];
if (canceled) {
[self.URLOperations removeObjectForKey:url];
}
}
UNLOCK(self.operationsLock);
}

//给下载过程添加进度
/**
1.生成URLOperations字典 下载url作为key value是具体的下载operation
2.将操作添加到操作队列中
3.将进度progressBlock和下载结束completedBlock封装成字典SDCallbacksDictionary,装入数组callbackBlocks,
4.生成token标识,并返回token
*/
- (nullable SDWebImageDownloadToken *)addProgressCallback:(SDWebImageDownloaderProgressBlock)progressBlock
completedBlock:(SDWebImageDownloaderCompletedBlock)completedBlock
forURL:(nullable NSURL *)url
createCallback:(SDWebImageDownloaderOperation *(^)(void))createCallback {
// url作为回调字典里的key,因此url不能为空,如果url为空则立即调用已完成代码块(image、imageData都传nil),并直接返回
if (url == nil) {
if (completedBlock != nil) {
completedBlock(nil, nil, nil, NO);
}
return nil;
}

LOCK(self.operationsLock);
获取url下载的操作
SDWebImageDownloaderOperation *operation = [self.URLOperations objectForKey:url];
// 有一种情况是操作可能被标记为已完成,但未从“self.URLOperations”中删除。
if (!operation || operation.isFinished) {
//创建一个operation,并且添加到URLOperation中
operation = createCallback();
__weak typeof(self) wself = self;

//设置operation操作完成以后的回调
operation.completionBlock = ^{
__strong typeof(wself) sself = wself;
if (!sself) {
return;
}
LOCK(sself.operationsLock);
//完成以后从URLOperations中移除该URL的operation操作
[sself.URLOperations removeObjectForKey:url];
UNLOCK(sself.operationsLock);
};

//将该url的operation操作添加到URLOperations中
[self.URLOperations setObject:operation forKey:url];

//仅在根据Apple的doc完成所有配置后才将操作添加到操作队列。`addOperation:`不同步执行`operation.completionBlock`,所以这不会导致死锁。
//把operation添加进入NSOperationQueue中,当operation添加到downloadQueue,会触发相应的start方法,开始下载。
[self.downloadQueue addOperation:operation];
}
UNLOCK(self.operationsLock);

//获取downloadOperationCancelToken
id downloadOperationCancelToken = [operation addHandlersForProgress:progressBlock completed:completedBlock];

//创建一个新的token,给各属性赋值,并返回token
SDWebImageDownloadToken *token = [SDWebImageDownloadToken new];
token.downloadOperation = operation;
token.url = url;
token.downloadOperationCancelToken = downloadOperationCancelToken;

return token;
}

//设置下载队列全部暂停/开始
- (void)setSuspended:(BOOL)suspended {
self.downloadQueue.suspended = suspended;
}

//全部取消下载队列中的下载操作
- (void)cancelAllDownloads {
[self.downloadQueue cancelAllOperations];
}


#pragma mark Helper methods
//根据task获取下载操作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;
}

NSURLSessionDataDelegate

当收到数据的时候,会触发这些代理方法,最后调用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
- (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];
if ([dataOperation respondsToSelector:@selector(URLSession:dataTask:didReceiveResponse:completionHandler:)]) {
[dataOperation URLSession:session dataTask:dataTask didReceiveResponse:response completionHandler:completionHandler];
} else {
if (completionHandler) {
completionHandler(NSURLSessionResponseAllow);
}
}
}

- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data {

// Identify the operation that runs this task and pass it the delegate method
SDWebImageDownloaderOperation *dataOperation = [self operationWithTask:dataTask];
if ([dataOperation respondsToSelector:@selector(URLSession:dataTask:didReceiveData:)]) {
[dataOperation URLSession:session dataTask:dataTask didReceiveData:data];
}
}

- (void)URLSession:(NSURLSession *)session
dataTask:(NSURLSessionDataTask *)dataTask
willCacheResponse:(NSCachedURLResponse *)proposedResponse
completionHandler:(void (^)(NSCachedURLResponse *cachedResponse))completionHandler {

// Identify the operation that runs this task and pass it the delegate method
SDWebImageDownloaderOperation *dataOperation = [self operationWithTask:dataTask];
if ([dataOperation respondsToSelector:@selector(URLSession:dataTask:willCacheResponse:completionHandler:)]) {
[dataOperation URLSession:session dataTask:dataTask willCacheResponse:proposedResponse completionHandler:completionHandler];
} else {
if (completionHandler) {
completionHandler(proposedResponse);
}
}
}

NSURLSessionTaskDelegate

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
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {

// Identify the operation that runs this task and pass it the delegate method
SDWebImageDownloaderOperation *dataOperation = [self operationWithTask:task];
if ([dataOperation respondsToSelector:@selector(URLSession:task:didCompleteWithError:)]) {
[dataOperation URLSession:session task:task didCompleteWithError:error];
}
}

- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task willPerformHTTPRedirection:(NSHTTPURLResponse *)response newRequest:(NSURLRequest *)request completionHandler:(void (^)(NSURLRequest * _Nullable))completionHandler {

// Identify the operation that runs this task and pass it the delegate method
SDWebImageDownloaderOperation *dataOperation = [self operationWithTask:task];
if ([dataOperation respondsToSelector:@selector(URLSession:task:willPerformHTTPRedirection:newRequest:completionHandler:)]) {
[dataOperation URLSession:session task:task willPerformHTTPRedirection:response newRequest:request completionHandler:completionHandler];
} else {
if (completionHandler) {
completionHandler(request);
}
}
}

- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler {

// Identify the operation that runs this task and pass it the delegate method
SDWebImageDownloaderOperation *dataOperation = [self operationWithTask:task];
if ([dataOperation respondsToSelector:@selector(URLSession:task:didReceiveChallenge:completionHandler:)]) {
[dataOperation URLSession:session task:task didReceiveChallenge:challenge completionHandler:completionHandler];
} else {
if (completionHandler) {
completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil);
}
}
}