一、总览
本类是一个单利对象,作用是生成一个图片的下载任务,既创建一个 SDWebImageDownloaderOperation。
二、头文件的声明
#import
#import "SDWebImageCompat.h"
#import "SDWebImageOperation.h"
typedef NS_OPTIONS(NSUInteger, SDWebImageDownloaderOptions) {
SDWebImageDownloaderLowPriority = 1 << 0,
SDWebImageDownloaderProgressiveDownload = 1 << 1,
/**
* By default, request prevent the use of NSURLCache. With this flag, NSURLCache
* is used with default policies.
*/
/*
*默认情况下,http请求阻止使用NSURLCache对象。如果设置了这个标记,则NSURLCache会被http请求使用。
*/
SDWebImageDownloaderUseNSURLCache = 1 << 2,
/**
* Call completion block with nil image/imageData if the image was read from NSURLCache
* (to be combined with `SDWebImageDownloaderUseNSURLCache`).
* I think this option should be renamed to 'SDWebImageDownloaderUsingCachedResponseDontLoad'
*/
/*
*如果image/imageData是从NSURLCache返回的。则completion这个回调会返回nil。
*/
SDWebImageDownloaderIgnoreCachedResponse = 1 << 3,
/**
* 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.
*/
/*
*如果app进入后台模式,是否继续下载。这个是通过在后台申请时间来完成这个操作。如果指定的时间范围内没有完成,则直接取消下载。
*/
SDWebImageDownloaderContinueInBackground = 1 << 4,
/**
* Handles cookies stored in NSHTTPCookieStore by setting
* NSMutableURLRequest.HTTPShouldHandleCookies = YES;
*/
/*
处理缓存在`NSHTTPCookieStore`对象里面的cookie。通过设置`NSMutableURLRequest.HTTPShouldHandleCookies = YES`来实现的。
*/
SDWebImageDownloaderHandleCookies = 1 << 5,
/**
* Enable to allow untrusted SSL certificates.
* Useful for testing purposes. Use with caution in production.
*/
/*
*允许非信任的SSL证书请求。
*在测试的时候很有用。但是正式环境要小心使用。
*/
SDWebImageDownloaderAllowInvalidSSLCertificates = 1 << 6,
/**
* Put the image in the high priority queue.
*/
/*
* 默认情况下,图片加载的顺序是根据加入队列的顺序加载的。但是这个标记会把任务加入队列的最前面。
*/
SDWebImageDownloaderHighPriority = 1 << 7,
/**
* Scale down the image
*/
/*
*默认情况下,图片会按照他的原始大小来解码显示。这个属性会调整图片的尺寸到合适的大小根据设备的内存限制。
*如果`SDWebImageProgressiveDownload`标记被设置了,则这个flag不起作用。
*/
SDWebImageDownloaderScaleDownLargeImages = 1 << 8,
};
/**
图片通过`SDWebImageDownloader`下载的时候。是FIFO或者LIFO模式。默认是FIFO模式。
*/
typedef NS_ENUM(NSInteger, SDWebImageDownloaderExecutionOrder) {
/**
* Default value. All download operations will execute in queue style (first-in-first-out).
*/
SDWebImageDownloaderFIFOExecutionOrder,
/**
* All download operations will execute in stack style (last-in-first-out).
*/
SDWebImageDownloaderLIFOExecutionOrder
};
extern NSString * _Nonnull const SDWebImageDownloadStartNotification;
extern NSString * _Nonnull const SDWebImageDownloadStopNotification;
typedef void(^SDWebImageDownloaderProgressBlock)(NSInteger receivedSize, NSInteger expectedSize, NSURL * _Nullable targetURL);
typedef void(^SDWebImageDownloaderCompletedBlock)(UIImage * _Nullable image, NSData * _Nullable data, NSError * _Nullable error, BOOL finished);
typedef NSDictionary SDHTTPHeadersDictionary;
typedef NSMutableDictionary SDHTTPHeadersMutableDictionary;
typedef SDHTTPHeadersDictionary * _Nullable (^SDWebImageDownloaderHeadersFilterBlock)(NSURL * _Nullable url, SDHTTPHeadersDictionary * _Nullable headers);
/**
* A token associated with each download. Can be used to cancel a download
*/
/**
给url对应的下载operation关联一个token。方便后面取消下载。
*/
@interface SDWebImageDownloadToken : NSObject
@property (nonatomic, strong, nullable) NSURL *url;
@property (nonatomic, strong, nullable) id downloadOperationCancelToken;
@end
/**
* Asynchronous downloader dedicated and optimized for image loading.
*/
/**
专注与异步下载图片并且设置图片下载选项。
*/
@interface SDWebImageDownloader : NSObject
/**
* Decompressing images that are downloaded and cached can improve performance but can consume lot of memory.
* Defaults to YES. Set this to NO if you are experiencing a crash due to excessive memory consumption.
*/
/**
* 当图片下载完成以后,加压缩图片以后再换成。这样可以提升性能但是会占用更多的存储空间。
* 模式YES,如果你因为过多的内存消耗导致一个奔溃,可以把这个属性设置为NO。
*/
@property (assign, nonatomic) BOOL shouldDecompressImages;
/**
* The maximum number of concurrent downloads
*/
/**
最大并行下载的数量
*/
@property (assign, nonatomic) NSInteger maxConcurrentDownloads;
/**
* Shows the current amount of downloads that still need to be downloaded
*/
/**
当前并行下载数量
*/
@property (readonly, nonatomic) NSUInteger currentDownloadCount;
/**
* The timeout value (in seconds) for the download operation. Default: 15.0.
*/
/**
下载超时时间设置
*/
@property (assign, nonatomic) NSTimeInterval downloadTimeout;
/**
* Changes download operations execution order. Default value is `SDWebImageDownloaderFIFOExecutionOrder`.
*/
/**
改变下载operation的执行顺序。默认是FIFO。
*/
@property (assign, nonatomic) SDWebImageDownloaderExecutionOrder executionOrder;
/**
* Singleton method, returns the shared instance
*
* @return global shared instance of downloader class
*/
/**
单列方法。返回一个单列对象
@return 返回一个单列的SDWebImageDownloader对象
*/
+ (nonnull instancetype)sharedDownloader;
/**
* Set the default URL credential to be set for request operations.
*/
/**
为图片加载request设置一个SSL证书对象。
*/
@property (strong, nonatomic, nullable) NSURLCredential *urlCredential;
/**
* Set username
*/
/**
Basic认证请求设置用户名和密码
*/
@property (strong, nonatomic, nullable) NSString *username;
/**
* Set password
*/
@property (strong, nonatomic, nullable) NSString *password;
/**
* Set filter to pick headers for downloading image HTTP request.
*
* This block will be invoked for each downloading image request, returned
* NSDictionary will be used as headers in corresponding HTTP request.
*/
/**
* 为http请求设置header。
* 每一request执行的时候,这个Block都会被执行。用于向http请求添加请求域。
*/
@property (nonatomic, copy, nullable) SDWebImageDownloaderHeadersFilterBlock headersFilter;
/**
* Creates an instance of a downloader with specified session configuration.
* *Note*: `timeoutIntervalForRequest` is going to be overwritten.
* @return new instance of downloader class
*/
/**
初始化一个请求对象
@param sessionConfiguration NSURLSessionTask初始化配置
@return 返回一个SDWebImageDownloader对象
*/
- (nonnull instancetype)initWithSessionConfiguration:(nullable NSURLSessionConfiguration *)sessionConfiguration NS_DESIGNATED_INITIALIZER;
/**
* Set a value for a HTTP header to be appended to each download HTTP request.
*
* @param value The value for the header field. Use `nil` value to remove the header.
* @param field The name of the header field to set.
*/
/**
设置请求头域
@param value 请求头域值
@param field 请求头域名
*/
- (void)setValue:(nullable NSString *)value forHTTPHeaderField:(nullable NSString *)field;
/**
* Returns the value of the specified HTTP header field.
*
* @return The value associated with the header field field, or `nil` if there is no corresponding header field.
*/
- (nullable NSString *)valueForHTTPHeaderField:(nullable NSString *)field;
/**
* Sets a subclass of `SDWebImageDownloaderOperation` as the default
* `NSOperation` to be used each time SDWebImage constructs a request
* operation to download an image.
*
* @param operationClass The subclass of `SDWebImageDownloaderOperation` to set
* as default. Passing `nil` will revert to `SDWebImageDownloaderOperation`.
*/
/**
设置一个`SDWebImageDownloaderOperation`的子类作为`NSOperation`来构建request来下载一张图片。
@param operationClass 指定的子类
*/
- (void)setOperationClass:(nullable Class)operationClass;
/**
* Creates a SDWebImageDownloader async downloader instance with a given URL
*
* The delegate will be informed when the image is finish downloaded or an error has happen.
*
* @see SDWebImageDownloaderDelegate
*
* @param url The URL to the image to download
* @param options The options to be used for this download
* @param progressBlock A block called repeatedly while the image is downloading
* @note the progress block is executed on a background queue
* @param completedBlock A block called once the download is completed.
* If the download succeeded, the image parameter is set, in case of error,
* error parameter is set with the error. The last parameter is always YES
* if SDWebImageDownloaderProgressiveDownload isn't use. With the
* SDWebImageDownloaderProgressiveDownload option, this block is called
* repeatedly with the partial image object and the finished argument set to NO
* before to be called a last time with the full image and finished argument
* set to YES. In case of error, the finished argument is always YES.
*
* @return A token (SDWebImageDownloadToken) that can be passed to -cancel: to cancel this operation
*/
/**
创建一个`SDWebImageDownloader`的异步
@param url <#url description#>
@param options <#options description#>
@param progressBlock <#progressBlock description#>
@param completedBlock <#completedBlock description#>
@return <#return value description#>
*/
- (nullable SDWebImageDownloadToken *)downloadImageWithURL:(nullable NSURL *)url
options:(SDWebImageDownloaderOptions)options
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock;
/**
* Cancels a download that was previously queued using -downloadImageWithURL:options:progress:completed:
*
* @param token The token received from -downloadImageWithURL:options:progress:completed: that should be canceled.
*/
- (void)cancel:(nullable SDWebImageDownloadToken *)token;
/**
* Sets the download queue suspension state
*/
- (void)setSuspended:(BOOL)suspended;
/**
* Cancels all download operations in the queue
*/
- (void)cancelAllDownloads;
@end
三、实现文件
#import "SDWebImageDownloader.h"
#import "SDWebImageDownloaderOperation.h"
#import
@implementation SDWebImageDownloadToken
@end
@interface SDWebImageDownloader ()
/**
所有的下载图片的Operation都加入NSoperationQueue中
*/
@property (strong, nonatomic, nonnull) NSOperationQueue *downloadQueue;
/**
最后一个添加的Operation
*/
@property (weak, nonatomic, nullable) NSOperation *lastAddedOperation;
/**
自定义的NSOperation子类
*/
@property (assign, nonatomic, nullable) Class operationClass;
/**
用于记录url和他对应的SDWebImageDownloaderOperation对象。
*/
@property (strong, nonatomic, nonnull) NSMutableDictionary *URLOperations;
/**
请求头域字典
*/
@property (strong, nonatomic, nullable) SDHTTPHeadersMutableDictionary *HTTPHeaders;
// This queue is used to serialize the handling of the network responses of all the download operation in a single queue
@property (SDDispatchQueueSetterSementics, nonatomic, nullable) dispatch_queue_t barrierQueue;
// The session in which data tasks will run
/**
通过这个`NSURLSession`创建请求
*/
@property (strong, nonatomic) NSURLSession *session;
@end
@implementation SDWebImageDownloader
+ (void)initialize {
// Bind SDNetworkActivityIndicator if available (download it here: http://github.com/rs/SDNetworkActivityIndicator )
// To use it, just add #import "SDNetworkActivityIndicator.h" in addition to the SDWebImage import
if (NSClassFromString(@"SDNetworkActivityIndicator")) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
id activityIndicator = [NSClassFromString(@"SDNetworkActivityIndicator") performSelector:NSSelectorFromString(@"sharedActivityIndicator")];
#pragma clang diagnostic pop
// Remove observer in case it was previously added.
[[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];
}
}
+ (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
_barrierQueue = dispatch_queue_create("com.hackemist.SDWebImageDownloaderBarrierQueue", DISPATCH_QUEUE_CONCURRENT);
_downloadTimeout = 15.0;
sessionConfiguration.timeoutIntervalForRequest = _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];
}
return self;
}
- (void)dealloc {
[self.session invalidateAndCancel];
self.session = nil;
[self.downloadQueue cancelAllOperations];
SDDispatchQueueRelease(_barrierQueue);
}
/**
添加或者移除请求头域
@param value 请求头域值
@param field 请求头域名
*/
- (void)setValue:(nullable NSString *)value forHTTPHeaderField:(nullable NSString *)field {
if (value) {
self.HTTPHeaders[field] = value;
}
else {
[self.HTTPHeaders removeObjectForKey:field];
}
}
- (nullable NSString *)valueForHTTPHeaderField:(nullable NSString *)field {
return self.HTTPHeaders[field];
}
/**
设置NSOperationQueue并行下载的数量
@param maxConcurrentDownloads 下载数量
*/
- (void)setMaxConcurrentDownloads:(NSInteger)maxConcurrentDownloads {
_downloadQueue.maxConcurrentOperationCount = maxConcurrentDownloads;
}
- (NSUInteger)currentDownloadCount {
return _downloadQueue.operationCount;
}
- (NSInteger)maxConcurrentDownloads {
return _downloadQueue.maxConcurrentOperationCount;
}
/**
制定一个`SDWebImageDownloader`的子类作为下载类
@param operationClass 类名
*/
- (void)setOperationClass:(nullable Class)operationClass {
if (operationClass && [operationClass isSubclassOfClass:[NSOperation class]] && [operationClass conformsToProtocol:@protocol(SDWebImageDownloaderOperationInterface)]) {
_operationClass = operationClass;
} else {
_operationClass = [SDWebImageDownloaderOperation class];
}
}
/**
新建一个SDWebImageDownloadOperation对象来来做具体的下载操作。同时指定缓存策略、cookie策略、自定义请求头域等。
@param url url
@param options 加载选项
@param progressBlock 进度progress
@param completedBlock 完成回调
@return 返回一个SDWebImageDownloadToken,用于关联一个请求
*/
- (nullable SDWebImageDownloadToken *)downloadImageWithURL:(nullable NSURL *)url
options:(SDWebImageDownloaderOptions)options
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock {
__weak SDWebImageDownloader *wself = self;
return [self addProgressCallback:progressBlock completedBlock:completedBlock forURL:url createCallback:^SDWebImageDownloaderOperation *{
__strong __typeof (wself) sself = wself;
NSTimeInterval timeoutInterval = sself.downloadTimeout;
if (timeoutInterval == 0.0) {
timeoutInterval = 15.0;
}
// In order to prevent from potential duplicate caching (NSURLCache + SDImageCache) we disable the cache for image requests if told otherwise
/*
*为了避免可能存在的NSURLCache和SDImageCache同时缓存。我们默认不允许image对象的NSURLCache对象。
具体缓存策略参考http://www.jianshu.com/p/855c2c6e761f
*/
NSURLRequestCachePolicy cachePolicy = NSURLRequestReloadIgnoringLocalCacheData;
if (options & SDWebImageDownloaderUseNSURLCache) {
if (options & SDWebImageDownloaderIgnoreCachedResponse) {
cachePolicy = NSURLRequestReturnCacheDataDontLoad;
} else {
cachePolicy = NSURLRequestUseProtocolCachePolicy;
}
}
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.HTTPHeaders copy]);
}
else {
request.allHTTPHeaderFields = sself.HTTPHeaders;
}
//初始化一个自定义NSOperation对象
SDWebImageDownloaderOperation *operation = [[sself.operationClass alloc] initWithRequest:request inSession:sself.session options:options];
//是否解压缩返回的图片
operation.shouldDecompressImages = sself.shouldDecompressImages;
//指定验证信息
if (sself.urlCredential) {
//SSL验证
operation.credential = sself.urlCredential;
} else if (sself.username && sself.password) {
//Basic验证
operation.credential = [NSURLCredential credentialWithUser:sself.username password:sself.password persistence:NSURLCredentialPersistenceForSession];
}
//指定优先级
if (options & SDWebImageDownloaderHighPriority) {
operation.queuePriority = NSOperationQueuePriorityHigh;
} else if (options & SDWebImageDownloaderLowPriority) {
operation.queuePriority = NSOperationQueuePriorityLow;
}
//把operatin添加进入NSOperationQueue中
[sself.downloadQueue addOperation:operation];
/*
如果是LIFO这种模式,则需要手动指定operation之间的依赖关系
*/
if (sself.executionOrder == SDWebImageDownloaderLIFOExecutionOrder) {
// Emulate LIFO execution order by systematically adding new operations as last operation's dependency
//如果是LIFO,则让前面的operation依赖于最新添加的operation
[sself.lastAddedOperation addDependency:operation];
sself.lastAddedOperation = operation;
}
return operation;
}];
}
/**
移除一个图片加载操作
@param token 通过token来确定操作
*/
- (void)cancel:(nullable SDWebImageDownloadToken *)token {
dispatch_barrier_async(self.barrierQueue, ^{
SDWebImageDownloaderOperation *operation = self.URLOperations[token.url];
BOOL canceled = [operation cancel:token.downloadOperationCancelToken];
if (canceled) {
[self.URLOperations removeObjectForKey:token.url];
}
});
}
/**
给下载过程添加进度
@param progressBlock 进度Block
@param completedBlock 完成Block
@param url url地址
@param createCallback nil
@return 返回SDWebImageDownloadToken。方便后面取消
*/
- (nullable SDWebImageDownloadToken *)addProgressCallback:(SDWebImageDownloaderProgressBlock)progressBlock
completedBlock:(SDWebImageDownloaderCompletedBlock)completedBlock
forURL:(nullable NSURL *)url
createCallback:(SDWebImageDownloaderOperation *(^)())createCallback {
// 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 nil;
}
__block SDWebImageDownloadToken *token = nil;
dispatch_barrier_sync(self.barrierQueue, ^{
//看是否当前url是否有对应的Operation图片加载对象
SDWebImageDownloaderOperation *operation = self.URLOperations[url];
//如果没有,则直接创建一个。
if (!operation) {
//创建一个operation。并且添加到URLOperation中。
operation = createCallback();
self.URLOperations[url] = operation;
__weak SDWebImageDownloaderOperation *woperation = operation;
//设置operation操作完成以后的回调
operation.completionBlock = ^{
SDWebImageDownloaderOperation *soperation = woperation;
if (!soperation) return;
if (self.URLOperations[url] == soperation) {
[self.URLOperations removeObjectForKey:url];
};
};
}
id downloadOperationCancelToken = [operation addHandlersForProgress:progressBlock completed:completedBlock];
token = [SDWebImageDownloadToken new];
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对应的SDWebImageDownloaderOperation。
@param task task对象
@return SDWebImageDownloaderOperation对象
*/
- (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
- (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];
//调用`SDWebImageDownloaderOperation`类对应的方法来处理
[dataOperation URLSession:session dataTask:dataTask didReceiveResponse:response completionHandler:completionHandler];
}
- (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];
//调用`SDWebImageDownloaderOperation`类对应的方法来处理
[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];
//调用`SDWebImageDownloaderOperation`类对应的方法来处理
[dataOperation URLSession:session dataTask:dataTask willCacheResponse:proposedResponse completionHandler:completionHandler];
}
#pragma mark NSURLSessionTaskDelegate
- (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];
//调用`SDWebImageDownloaderOperation`类对应的方法来处理
[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 {
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];
//调用`SDWebImageDownloaderOperation`类对应的方法来处理
[dataOperation URLSession:session task:task didReceiveChallenge:challenge completionHandler:completionHandler];
}
@end