SDWebImageDownloaderOperation
SDWebImage中自定义下载任务,该类封装单个图片下载操作,需要外部传入NSURLRequest,NSURLSessionTask完成下载任务。该类继承NSOperation,同时遵守SDWebImageDownloaderOperationInterface协议,该协议可以为下载器统一接口。遵守的协议下方法在.h文件中声明方便调用者使用。
__deprecated_msg(“reason message”) 方法属性失效声明修饰属性、方法
NS_DESIGNATED_INITIALIZER 指定初始化方法修饰方法
未操作枚举的使用 位运算符 | & ! ^
初始化传入的属性如果需要暴露给调用方使用只读属性限制客户端权限
@protocol SDWebImageDownloaderOperationInterface<NSObject>
// 初始化方法
- (nonnull instancetype)initWithRequest:(nullable NSURLRequest *)request
inSession:(nullable NSURLSession *)session
options:(SDWebImageDownloaderOptions)options;
// 为下载添加下载进程完成回调
- (nullable id)addHandlersForProgress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock;
// 是否需要为下载后的文件进行解码
- (BOOL)shouldDecompressImages;
- (void)setShouldDecompressImages:(BOOL)value;
// https证书
- (nullable NSURLCredential *)credential;
- (void)setCredential:(nullable NSURLCredential *)value;
@end
SDWebImageDownloaderOperation.h 文件
// 通知名称声明 见名知意
FOUNDATION_EXPORT NSString * _Nonnull const SDWebImageDownloadStartNotification;
FOUNDATION_EXPORT NSString * _Nonnull const SDWebImageDownloadReceiveResponseNotification;
FOUNDATION_EXPORT NSString * _Nonnull const SDWebImageDownloadStopNotification;
FOUNDATION_EXPORT NSString * _Nonnull const SDWebImageDownloadFinishNotification;
@interface SDWebImageDownloaderOperation : NSOperation <SDWebImageDownloaderOperationInterface, SDWebImageOperation, NSURLSessionTaskDelegate, NSURLSessionDataDelegate>
/**
* The request used by the operation's task.
*/
@property (strong, nonatomic, readonly, nullable) NSURLRequest *request;
/**
* The operation's task
*/
@property (strong, nonatomic, readonly, nullable) NSURLSessionTask *dataTask;
// 是否给图片解码 默认给图片进行解码可提高性能但是消耗内存
@property (assign, nonatomic) BOOL shouldDecompressImages;
// 没有使用该属性,兼容使用
@property (nonatomic, assign) BOOL shouldUseCredentialStorage __deprecated_msg("Property deprecated. Does nothing. Kept only for backwards compatibility");
// 身份验证挑战的凭据。这将被请求URL的用户名或密码(如果存在)所存在的任何共享凭据覆盖。(需要进一步了解)
@property (nonatomic, strong, nullable) NSURLCredential *credential;
/**
SDWebImageDownloaderLowPriority = 1 << 0,
SDWebImageDownloaderProgressiveDownload = 1 << 1, // 带有进度
SDWebImageDownloaderUseNSURLCache = 1 << 2, // 使用URLCache
SDWebImageDownloaderIgnoreCachedResponse = 1 << 3, // 不缓存响应
SDWebImageDownloaderContinueInBackground = 1 << 4, // 支持后台下载
SDWebImageDownloaderHandleCookies = 1 << 5, // 使用Cookies
SDWebImageDownloaderAllowInvalidSSLCertificates = 1 << 6, // 允许验证SSL
SDWebImageDownloaderHighPriority = 1 << 7, // 高权限
SDWebImageDownloaderScaleDownLargeImages = 1 << 8, // 裁剪大图片
* 位操作枚举 eg:options & SDWebImageDownloaderLowPriority
*/
@property (assign, nonatomic, readonly) SDWebImageDownloaderOptions options;
/**
* 预期文件大小,从请求回调didReceiveResponse中响应参数NSURLResponse中获取,只有该值大于0的时候下载进度回调函数才会执行,计算下载进度。
*/
@property (assign, nonatomic) NSInteger expectedSize;
// 请求返回响应
@property (strong, nonatomic, nullable) NSURLResponse *response;
/**
* NS_DESIGNATED_INITIALIZER 指定初始化方法
* 传入 NSURLRequest NSURLSession 完成下载操作 该参数作为该类的制度属性
*/
- (nonnull instancetype)initWithRequest:(nullable NSURLRequest *)request
inSession:(nullable NSURLSession *)session
options:(SDWebImageDownloaderOptions)options NS_DESIGNATED_INITIALIZER;
/**
添加下载进度与下载完成回调
返回一个字典
key:kProgressCallbackKey value:下载进度block
key:kCompletedCallbackKey value:下载完成block
*/
- (nullable id)addHandlersForProgress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock;
// 取消
- (BOOL)cancel:(nullable id)token;
@end
SDWebImageDownloaderOperation.m 实现文件
NSString *const SDWebImageDownloadStartNotification = @"SDWebImageDownloadStartNotification";
NSString *const SDWebImageDownloadReceiveResponseNotification = @"SDWebImageDownloadReceiveResponseNotification";
NSString *const SDWebImageDownloadStopNotification = @"SDWebImageDownloadStopNotification";
NSString *const SDWebImageDownloadFinishNotification = @"SDWebImageDownloadFinishNotification";
// 声明一个存储回调函数的字典中的key
static NSString *const kProgressCallbackKey = @"progress";
static NSString *const kCompletedCallbackKey = @"completed";
// 声明一个存储回调函数的字典
typedef NSMutableDictionary<NSString *, id> SDCallbacksDictionary;
@interface SDWebImageDownloaderOperation ()
// 包含下载操作回调block的数组
@property (strong, nonatomic, nonnull) NSMutableArray<SDCallbacksDictionary *> *callbackBlocks;
// ===================== 自定义option 重写属性kvo监听 ================
// 正在执行下载
@property (assign, nonatomic, getter = isExecuting) BOOL executing;
// 下载完成
@property (assign, nonatomic, getter = isFinished) BOOL finished;
//===================================================================
// 获取数据data需要拼接
@property (strong, nonatomic, nullable) NSMutableData *imageData;
// NSUrlCache缓存的数据
@property (copy, nonatomic, nullable) NSData *cachedData;
// 外部注入的NSURLSession下载会话, SDWebImageDownloader注入时已经强引用,所以用weak
@property (weak, nonatomic, nullable) NSURLSession *unownedSession;
// 如果外部没有注入NSURLSession,由当前类实例化一个NSURLSession并且,负责该session的生命周期
@property (strong, nonatomic, nullable) NSURLSession *ownedSession;
//通过UrlRequest生成的dataTask请求任务
@property (strong, nonatomic, readwrite, nullable) NSURLSessionTask *dataTask;
//为保证在多线程环境下操作callbackBlocks的数据安全提供的并发队列
@property (SDDispatchQueueSetterSementics, nonatomic, nullable) dispatch_queue_t barrierQueue;
#if SD_UIKIT
//app退到后台后向UIApplication注册的后台任务标识,可以获得一些额外的下载时间
@property (assign, nonatomic) UIBackgroundTaskIdentifier backgroundTaskId;
#endif
//对图片进行渐进式解码的解码器
@property (strong, nonatomic, nullable) id<SDWebImageProgressiveCoder> progressiveCoder;
@end
@implementation SDWebImageDownloaderOperation
@synthesize executing = _executing;
@synthesize finished = _finished;
- (nonnull instancetype)init {
return [self initWithRequest:nil inSession:nil options:0];
}
// 指定初始化方法(NS_DESIGNATED_INITIALIZER)需要在其中掉用父类的指定初始化方法。该类中其他的初始化方法调用该方法。
- (nonnull instancetype)initWithRequest:(nullable NSURLRequest *)request
inSession:(nullable NSURLSession *)session
options:(SDWebImageDownloaderOptions)options {
if ((self = [super init])) {
_executing = NO;
_finished = NO;
_expectedSize = 0;
// 默认给图片进行解码
_shouldDecompressImages = YES;
_options = options;
_request = [request copy];
_callbackBlocks = [NSMutableArray new];
_unownedSession = session;
_barrierQueue = dispatch_queue_create("com.hackemist.SDWebImageDownloaderOperationBarrierQueue", DISPATCH_QUEUE_CONCURRENT);
}
return self;
}
- (void)dealloc {
SDDispatchQueueRelease(_barrierQueue);
}
/**
下载进度 完成 callback 存储到 数组中(callbackBlocks)可能会空
这里使用了 dispatch_barrier_async !!!
对这个函数的调用总是在块被提交之后立即返回,而不是等待块被调用。当barrier块到达私有并发队列的前端时,不会立即执行它。相反,队列等待,直到当前执行的块完成执行。此时,barrier块将自己执行。在barrier块完成之前,不会执行barrier块之后提交的任何块。
您指定的队列应该是您自己使用dispatch_queue_create函数创建的并发队列。如果传递给此函数的队列是串行队列或全局并发队列之一,则此函数的行为类似于dispatch_async函数。
拓展: 实现高吞吐量的数据读写保证线程安全
*/
- (nullable id)addHandlersForProgress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock {
SDCallbacksDictionary *callbacks = [NSMutableDictionary new];
if (progressBlock) callbacks[kProgressCallbackKey] = [progressBlock copy];
if (completedBlock) callbacks[kCompletedCallbackKey] = [completedBlock copy];
dispatch_barrier_async(self.barrierQueue, ^{
[self.callbackBlocks addObject:callbacks];
});
return callbacks;
//block he callbacks 同步执行
}
// 通过key获取回调block
- (nullable NSArray<id> *)callbacksForKey:(NSString *)key {
__block NSMutableArray<id> *callbacks = nil;
dispatch_sync(self.barrierQueue, ^{
// dispatch_sync 同步执行 移出callbacks中空回调[NSNull null](下载进度与下载完成)
callbacks = [[self.callbackBlocks valueForKey:key] mutableCopy];
/**
所有value组成数组 非该key的vale 为[NSNull null]
*/
[callbacks removeObjectIdenticalTo:[NSNull null]];
});
// 取出key所对应的所有block
return [callbacks copy];
}
/**
dispatch_barrier_sync
向调度队列提交屏障块以便同步执行。与dispatch_barrier_async不同,这个函数在barrier块完成之前不会返回。调用此函数并针对当前队列会导致死锁。
当barrier块到达私有并发队列的前端时,不会立即执行它。相反,队列等待,直到当前执行的块完成执行。此时,队列将自己执行barrier块。在barrier块完成之前,不会执行barrier块之后提交的任何块。
您指定的队列应该是您自己使用dispatch_queue_create函数创建的并发队列。如果传递给此函数的队列是串行队列或全局并发队列之一,则此函数的行为类似于dispatch_sync函数。
与dispatch_barrier_async不同,目标队列上不执行retain。因为对这个函数的调用是同步的,所以它“借用”了调用者的引用。此外,块上不执行Block_copy。
作为一种优化,这个函数在可能的情况下调用当前线程上的barrier块。
*/
- (BOOL)cancel:(nullable id)token {
__block BOOL shouldCancel = NO;
// 同步执行
dispatch_barrier_sync(self.barrierQueue, ^{
[self.callbackBlocks removeObjectIdenticalTo:token];
// 所有回调都移除了说明下载都完成了。
if (self.callbackBlocks.count == 0) {
shouldCancel = YES;
}
});
if (shouldCancel) {
[self cancel];
}
return shouldCancel;
}
// 当SDWebImageDownloaderOperation添加到并发队列,就会调用start方法。
- (void)start {
@synchronized (self) {
//确认当前下载状态
if (self.isCancelled) {
//如果当前下载状态是完成 则设置下载完成状态为YES 并且结束下载任务
self.finished = YES;
[self reset];
return;
}
#pragma mark- --注册后台任务
#if SD_UIKIT
Class UIApplicationClass = NSClassFromString(@"UIApplication");
BOOL hasApplication = UIApplicationClass && [UIApplicationClass respondsToSelector:@selector(sharedApplication)];
if (hasApplication && [self shouldContinueWhenAppEntersBackground]) {
//当app即将退到后台,想UIApplication注册一个后台执行的任务,以获取额外的操作时间
__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
if (self.options & SDWebImageDownloaderIgnoreCachedResponse) {
// 获取缓存的响应
NSCachedURLResponse *cachedResponse = [[NSURLCache sharedURLCache] cachedResponseForRequest:self.request];
if (cachedResponse) {
//拿到NSUrlCache缓存的响应对应的数据
self.cachedData = cachedResponse.data;
}
}
NSURLSession *session = self.unownedSession;
if (!self.unownedSession) {
NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration];
sessionConfig.timeoutIntervalForRequest = 15;
self.ownedSession = [NSURLSession sessionWithConfiguration:sessionConfig
delegate:self
delegateQueue:nil];
session = self.ownedSession;
}
self.dataTask = [session dataTaskWithRequest:self.request];
self.executing = YES;
}
//开始下载任务
[self.dataTask resume];
if (self.dataTask) {
for (SDWebImageDownloaderProgressBlock progressBlock in [self callbacksForKey:kProgressCallbackKey]) {
progressBlock(0, NSURLResponseUnknownLength, self.request.URL);
}
__weak typeof(self) weakSelf = self;
//下载开始通知
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStartNotification object:weakSelf];
});
} else {
//如果下载任务实例化失败,则以错误的状态调用completedBlock回调
[self callCompletionBlocksWithError:[NSError errorWithDomain:NSURLErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey : @"Connection can't be initialized"}]];
}
#pragma mark - 从"flag:注册后台任务"到这行代码区间的代码,就是app退到后台向UIApplication注册的backgroundTask
#if SD_UIKIT
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
}
- (void)cancel {
@synchronized (self) {
[self cancelInternal];
}
}
- (void)cancelInternal {
if (self.isFinished) return;
//把operation置为取消状态
[super cancel];
if (self.dataTask) {
//取消下载任务
[self.dataTask cancel];
__weak typeof(self) weakSelf = self;
// 回到主线程发送下载完成通知
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStopNotification object:weakSelf];
});
if (self.isExecuting) self.executing = NO;
if (!self.isFinished) self.finished = YES;
}
[self reset];
}
- (void)done {
self.finished = YES;
self.executing = NO;
[self reset];
}
// 主要就是对取消之后资源的管理 和状态的维护
- (void)reset {
__weak typeof(self) weakSelf = self;
// 清空下载回调
dispatch_barrier_async(self.barrierQueue, ^{
[weakSelf.callbackBlocks removeAllObjects];
});
// 释放请求任务
self.dataTask = nil;
// 释放session
NSOperationQueue *delegateQueue;
if (self.unownedSession) {
delegateQueue = self.unownedSession.delegateQueue;
} else {
delegateQueue = self.ownedSession.delegateQueue;
}
if (delegateQueue) {
NSAssert(delegateQueue.maxConcurrentOperationCount == 1, @"NSURLSession delegate queue should be a serial queue");
[delegateQueue addOperationWithBlock:^{
weakSelf.imageData = nil;
}];
}
if (self.ownedSession) {
// 释放操作
[self.ownedSession invalidateAndCancel];
self.ownedSession = nil;
}
}
// 任务完成状态
- (void)setFinished:(BOOL)finished {
// 通知观察对象给定属性的值即将更改。
[self willChangeValueForKey:@"isFinished"];
_finished = finished;
// 通知观察对象给定属性的值已经更改。
[self didChangeValueForKey:@"isFinished"];
}
// 任务执行状态
- (void)setExecuting:(BOOL)executing {
// 通知观察对象给定属性的值即将更改。
[self willChangeValueForKey:@"isExecuting"];
_executing = executing;
// 通知观察对象给定属性的值已经更改。
[self didChangeValueForKey:@"isExecuting"];
}
- (BOOL)isConcurrent {
return YES;
}
#pragma mark NSURLSessionDataDelegate
//接受到服务器响应数据
- (void)URLSession:(NSURLSession *)session
dataTask:(NSURLSessionDataTask *)dataTask
didReceiveResponse:(NSURLResponse *)response
completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler {
//'304 Not Modified' is an exceptional one
if (![response respondsToSelector:@selector(statusCode)] || (((NSHTTPURLResponse *)response).statusCode < 400 && ((NSHTTPURLResponse *)response).statusCode != 304)) {
NSInteger expected = (NSInteger)response.expectedContentLength;
expected = expected > 0 ? expected : 0;
self.expectedSize = expected;
for (SDWebImageDownloaderProgressBlock progressBlock in [self callbacksForKey:kProgressCallbackKey]) {
progressBlock(0, expected, self.request.URL);
}
self.imageData = [[NSMutableData alloc] initWithCapacity:expected];
self.response = response;
__weak typeof(self) weakSelf = self;
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadReceiveResponseNotification object:weakSelf];
});
} else {
NSUInteger code = ((NSHTTPURLResponse *)response).statusCode;
if (code == 304) {
[self cancelInternal];
} else {
[self.dataTask cancel];
}
__weak typeof(self) weakSelf = self;
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStopNotification object:weakSelf];
});
[self callCompletionBlocksWithError:[NSError errorWithDomain:NSURLErrorDomain code:((NSHTTPURLResponse *)response).statusCode userInfo:nil]];
[self done];
}
if (completionHandler) {
completionHandler(NSURLSessionResponseAllow);
}
}
//接受到下载的数据包
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data {
[self.imageData appendData:data];
if ((self.options & SDWebImageDownloaderProgressiveDownload) && self.expectedSize > 0) {
// Get the image data
NSData *imageData = [self.imageData copy];
// Get the total bytes downloaded
const NSInteger totalSize = imageData.length;
// Get the finish status
BOOL finished = (totalSize >= self.expectedSize);
if (!self.progressiveCoder) {
// We need to create a new instance for progressive decoding to avoid conflicts
for (id<SDWebImageCoder>coder in [SDWebImageCodersManager sharedInstance].coders) {
if ([coder conformsToProtocol:@protocol(SDWebImageProgressiveCoder)] &&
[((id<SDWebImageProgressiveCoder>)coder) canIncrementallyDecodeFromData:imageData]) {
self.progressiveCoder = [[[coder class] alloc] init];
break;
}
}
}
UIImage *image = [self.progressiveCoder incrementallyDecodedImageWithData:imageData finished:finished];
if (image) {
NSString *key = [[SDWebImageManager sharedManager] cacheKeyForURL:self.request.URL];
image = [self scaledImageForKey:key image:image];
if (self.shouldDecompressImages) {
image = [[SDWebImageCodersManager sharedInstance] decompressedImageWithImage:image data:&data options:@{SDWebImageCoderScaleDownLargeImagesKey: @(NO)}];
}
[self callCompletionBlocksWithImage:image imageData:nil error:nil finished:NO];
}
}
for (SDWebImageDownloaderProgressBlock progressBlock in [self callbacksForKey:kProgressCallbackKey]) {
progressBlock(self.imageData.length, self.expectedSize, self.request.URL);
}
}
- (void)URLSession:(NSURLSession *)session
dataTask:(NSURLSessionDataTask *)dataTask
willCacheResponse:(NSCachedURLResponse *)proposedResponse
completionHandler:(void (^)(NSCachedURLResponse *cachedResponse))completionHandler {
NSCachedURLResponse *cachedResponse = proposedResponse;
if (self.request.cachePolicy == NSURLRequestReloadIgnoringLocalCacheData) {
// Prevents caching of responses
cachedResponse = nil;
}
if (completionHandler) {
completionHandler(cachedResponse);
}
}
#pragma mark NSURLSessionTaskDelegate
//下载完成的回调
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
@synchronized(self) {
self.dataTask = nil;
__weak typeof(self) weakSelf = self;
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStopNotification object:weakSelf];
if (!error) {
[[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadFinishNotification object:weakSelf];
}
});
}
if (error) {
[self callCompletionBlocksWithError:error];
} else {
if ([self callbacksForKey:kCompletedCallbackKey].count > 0) {
/**
* If you specified to use `NSURLCache`, then the response you get here is what you need.
*/
NSData *imageData = [self.imageData copy];
if (imageData) {
/** if you specified to only use cached data via `SDWebImageDownloaderIgnoreCachedResponse`,
* then we should check if the cached data is equal to image data
*/
if (self.options & SDWebImageDownloaderIgnoreCachedResponse && [self.cachedData isEqualToData:imageData]) {
// call completion block with nil
[self callCompletionBlocksWithImage:nil imageData:nil error:nil finished:YES];
} else {
UIImage *image = [[SDWebImageCodersManager sharedInstance] decodedImageWithData:imageData];
NSString *key = [[SDWebImageManager sharedManager] cacheKeyForURL:self.request.URL];
image = [self scaledImageForKey:key image:image];
BOOL shouldDecode = YES;
// Do not force decoding animated GIFs and WebPs
if (image.images) {
shouldDecode = NO;
} else {
#ifdef SD_WEBP
SDImageFormat imageFormat = [NSData sd_imageFormatForImageData:imageData];
if (imageFormat == SDImageFormatWebP) {
shouldDecode = NO;
}
#endif
}
if (shouldDecode) {
if (self.shouldDecompressImages) {
BOOL shouldScaleDown = self.options & SDWebImageDownloaderScaleDownLargeImages;
image = [[SDWebImageCodersManager sharedInstance] decompressedImageWithImage:image data:&imageData options:@{SDWebImageCoderScaleDownLargeImagesKey: @(shouldScaleDown)}];
}
}
if (CGSizeEqualToSize(image.size, CGSizeZero)) {
[self callCompletionBlocksWithError:[NSError errorWithDomain:SDWebImageErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey : @"Downloaded image has 0 pixels"}]];
} else {
[self callCompletionBlocksWithImage:image imageData:imageData error:nil finished:YES];
}
}
} else {
[self callCompletionBlocksWithError:[NSError errorWithDomain:SDWebImageErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey : @"Image data is nil"}]];
}
}
}
[self done];
}
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler {
NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;
__block NSURLCredential *credential = nil;
if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
if (!(self.options & SDWebImageDownloaderAllowInvalidSSLCertificates)) {
disposition = NSURLSessionAuthChallengePerformDefaultHandling;
} else {
credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
disposition = NSURLSessionAuthChallengeUseCredential;
}
} else {
if (challenge.previousFailureCount == 0) {
if (self.credential) {
credential = self.credential;
disposition = NSURLSessionAuthChallengeUseCredential;
} else {
disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge;
}
} else {
disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge;
}
}
if (completionHandler) {
completionHandler(disposition, credential);
}
}
#pragma mark Helper methods
- (nullable UIImage *)scaledImageForKey:(nullable NSString *)key image:(nullable UIImage *)image {
return SDScaledImageForKey(key, image);
}
- (BOOL)shouldContinueWhenAppEntersBackground {
return self.options & SDWebImageDownloaderContinueInBackground;
}
- (void)callCompletionBlocksWithError:(nullable NSError *)error {
[self callCompletionBlocksWithImage:nil imageData:nil error:error finished:YES];
}
- (void)callCompletionBlocksWithImage:(nullable UIImage *)image
imageData:(nullable NSData *)imageData
error:(nullable NSError *)error
finished:(BOOL)finished {
NSArray<id> *completionBlocks = [self callbacksForKey:kCompletedCallbackKey];
dispatch_main_async_safe(^{
for (SDWebImageDownloaderCompletedBlock completedBlock in completionBlocks) {
completedBlock(image, imageData, error, finished);
}
});
}
@end