SDWebImage
的图片下载是由 SDWebImageDownloader
类实现的,该类是一个异步下载管理器。主要工作是下载相关配置项的管理,包括下载队列的先后顺序、最大下载任务数量控制、下载队列中的任务创建、取消、暂停等任务管理,以及其他的 HTTPS
和 HTTP Header
的设置。
而真正实现图片下载的是 SDWebImageDownloaderOperation
类。该类的 Operation
操作依赖系统提供的 NSURLConnection
类来实现图片的下载。在 SDWebImageDownloader
类中,将图片的下载操作封装成了自定义的一个类 SDWebImageDownloaderOperation
,然后添加到了操作队列中。当操作队列调用这个操作时,会调用操作对象的 - (void)start
方法,在重写的这个方法中,生成了任务对象dataTask
,并调用resume
开始执行任务。 因为SDWebImageDownloaderOperation
类遵守了dataTask
对象的协议,所以dataTask
执行的结果会通过代理方法进行回调。在代理方法中,获取并保存了服务器返回的数据,并在任务执行结束后,对数据进行解码、缩放和解压。处理完成后就进行回调。
梳理 : SDWebImageDownloaderOperation
是一个自定义、并行的 NSOperation
子类。这个子类主要实现的功能有: 由于只自定义的并行 NSOperation
,所以需要管理 executing
, finished
等各种属性的处理,并且手动触发KVO。 在 - (void)start
方法里面实现主要逻辑,在重写的这个方法中生成了任务对象dataTask
,并调用resume
开始执行任务。 在 NSURLSessionTaskDelegate
和 NSURLSessionDataDelegate
中处理数据的加载,以及进度Block的处理。 如果 unownedSession
属性因为某种原因是nil
,则手动初始化一个做网络请求。 在代理方法中对认证、数据拼装、完成回调Block
做处理。
通过发送 SDWebImageDownloadStopNotification
,SDWebImageDownloadFinishNotification
,SDWebImageDownloadReceiveResponseNotification
, SDWebImageDownloadStartNotification
来通知 Operation
的状态。
.h文件 四个通知,SDWebImageDownloaderOperation
在四种情况下会发送通知:
开始下载
接收到数据
停止下载
结束下载
1 2 3 4 FOUNDATION_EXPORT NSString * _Nonnull const SDWebImageDownloadStartNotification; FOUNDATION_EXPORT NSString * _Nonnull const SDWebImageDownloadReceiveResponseNotification; FOUNDATION_EXPORT NSString * _Nonnull const SDWebImageDownloadStopNotification; FOUNDATION_EXPORT NSString * _Nonnull const SDWebImageDownloadFinishNotification;
SDWebImageDownloaderOperationInterface
协议如果想要实现一个自定义的下载操作,就必须继承自 NSOperation
,同时实现 SDWebImageDownloaderOperationInterface
这个协议。
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 @protocol SDWebImageDownloaderOperationInterface<NSObject> //使用NSURLRequest,NSURLSession和SDWebImageDownloaderOptions初始化 - (nonnull instancetype)initWithRequest:(nullable NSURLRequest *)request inSession:(nullable NSURLSession *)session options:(SDWebImageDownloaderOptions)options; //可以为每一个NSOperation自由的添加相应对象 - (nullable id)addHandlersForProgress:(nullable SDWebImageDownloaderProgressBlock)progressBlock completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock; //是否需要解压图片 - (BOOL)shouldDecompressImages; //设置是否需要解压图片 - (void)setShouldDecompressImages:(BOOL)value; //证书 - (nullable NSURLCredential *)credential; //设置证书 - (void)setCredential:(nullable NSURLCredential *)value; //取消token - (BOOL)cancel:(nullable id)token; @end
属性声明 和 方法 SDWebImageDownloaderOperation
遵守 SDWebImageDownloaderOperationInterface
, SDWebImageOperation
, NSURLSessionTaskDelegate
, NSURLSessionDataDelegate
等协议
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 /** * 操作任务使用的请求。(外部只读) */ @property (strong, nonatomic, readonly, nullable) NSURLRequest *request; /** * 操作任务(外部只读) */ @property (strong, nonatomic, readonly, nullable) NSURLSessionTask *dataTask; //是否需要解压图片 @property (assign, nonatomic) BOOL shouldDecompressImages; /** * 用于确定URL连接是否应查询凭据存储以验证连接。 * @deprecated Not used for a couple of versions */ @property (nonatomic, assign) BOOL shouldUseCredentialStorage __deprecated_msg("Property deprecated. Does nothing. Kept only for backwards compatibility"); /** * The credential used for authentication challenges 在 `-URLSession:task:didReceiveChallenge:completionHandler:`中用于身份验证挑战的凭证. * * 如果存在请求URL的用户名或密码,则会覆盖此任何共享凭据。 */ @property (nonatomic, strong, nullable) NSURLCredential *credential; /** * SDWebImageDownloaderOptions选项 (外部只读) */ @property (assign, nonatomic, readonly) SDWebImageDownloaderOptions options; /** * 预期的数据大小。 */ @property (assign, nonatomic) NSInteger expectedSize; /** * 操作任务返回的响应。 */ @property (strong, nonatomic, nullable) NSURLResponse *response; /** * I初始化`SDWebImageDownloaderOperation` 对象 * * @see SDWebImageDownloaderOperation * * @param request URL请求 * @param session 将运行此操作的URL会话 * @param options 下载的选项 * * @return 初始化实例 */ - (nonnull instancetype)initWithRequest:(nullable NSURLRequest *)request inSession:(nullable NSURLSession *)session options:(SDWebImageDownloaderOptions)options NS_DESIGNATED_INITIALIZER; /** * 添加处理程序以进行和完成。 返回可以传递给-cancel的tokent:取消这组回调。 * * @param progressBlock 当新的数据块到达时执行的块。 * @note 在后台队列上执行进度块 * @param completedBlock 当下载完成时执行的代码块 * @note 已完成的代码块在主队列上执行以获得成功。如果发现错误,则代码块可能会在后台队列上执行 * * @return 用于取消这组处理程序的token */ - (nullable id)addHandlersForProgress:(nullable SDWebImageDownloaderProgressBlock)progressBlock completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock; /** * 取消一组回调. 一旦所有的回调被取消,该操作取消 * * @param token代表一组要取消的回调 * * @return 如果操作已停止,则为YES,因为这是要取消的最后一个token。 否则为NO。 */ - (BOOL)cancel:(nullable id)token;
.m文件 信号量实现锁机制,这种锁性能要比NSLock高
1 2 #define LOCK(lock) dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER); #define UNLOCK(lock) dispatch_semaphore_signal(lock);
静态全局变量
1 2 3 4 5 6 7 8 9 10 11 12 13 //开始下载的通知 NSString *const SDWebImageDownloadStartNotification = @"SDWebImageDownloadStartNotification"; //接受数据的通知 NSString *const SDWebImageDownloadReceiveResponseNotification = @"SDWebImageDownloadReceiveResponseNotification"; //停止下载的通知 NSString *const SDWebImageDownloadStopNotification = @"SDWebImageDownloadStopNotification"; //结束下载的通知 NSString *const SDWebImageDownloadFinishNotification = @"SDWebImageDownloadFinishNotification"; //静态全局变量作为下载进度block字典存储的key static NSString *const kProgressCallbackKey = @"progress"; //静态全局变量作为结束下载block字典存储的key static NSString *const kCompletedCallbackKey = @"completed";
属性声明 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 //回调Block列表 @property (strong, nonatomic, nonnull) NSMutableArray<SDCallbacksDictionary *> *callbackBlocks; //自定义并行Operation需要管理的两个属性。默认是readonly的,我们这里通过声明改为可修改的。方便我们在后面操作。默认情况下_executing和finished都是NO //是否正在执行 @property (assign, nonatomic, getter = isExecuting) BOOL executing; //是否结束 @property (assign, nonatomic, getter = isFinished) BOOL finished; //图片数据 @property (strong, nonatomic, nullable) NSMutableData *imageData; //缓存数据 @property (copy, nonatomic, nullable) NSData *cachedData; // for `SDWebImageDownloaderIgnoreCachedResponse` //通过SDWebImageDownloader传过来。所以这里是weak。因为他是通过SDWebImageDownloader管理的。 @property (weak, nonatomic, nullable) NSURLSession *unownedSession; // 如果unownedSession是nil,我们需要手动创建一个并且管理他的生命周期和代理方法 @property (strong, nonatomic, nullable) NSURLSession *ownedSession; //dataTask对象 @property (strong, nonatomic, readwrite, nullable) NSURLSessionTask *dataTask; //一个锁,以保持对`callbackBlocks`线程安全的访问 @property (strong, nonatomic, nonnull) dispatch_semaphore_t callbacksLock; // a lock to keep the access to `callbackBlocks` thread-safe //一个并行queue。用于控制数据的处理 @property (strong, nonatomic, nonnull) dispatch_queue_t coderQueue; // the queue to do image decoding #if SD_UIKIT //如果用户设置了后台继续加载选线。则通过backgroundTask来继续下载图片 @property (assign, nonatomic) UIBackgroundTaskIdentifier backgroundTaskId; #endif //图片解码器,有意思的是如果图片没有完全下载完成时也可以解码展示部分图片 @property (strong, nonatomic, nullable) id<SDWebImageProgressiveCoder> progressiveCoder; @end
方法实现 编译器自动添加get和set方法
1 2 @synthesize executing = _executing; @synthesize finished = _finished;
初始化 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 - (nonnull instancetype)init { return [self initWithRequest:nil inSession:nil options:0]; } - (nonnull instancetype)initWithRequest:(nullable NSURLRequest *)request inSession:(nullable NSURLSession *)session options:(SDWebImageDownloaderOptions)options { if ((self = [super init])) { _request = [request copy]; _shouldDecompressImages = YES; _options = options; _callbackBlocks = [NSMutableArray new]; //默认情况下_executing和finished都是NO _executing = NO; _finished = NO; _expectedSize = 0; _unownedSession = session; _callbacksLock = dispatch_semaphore_create(1); _coderQueue = dispatch_queue_create("com.hackemist.SDWebImageDownloaderOperationCoderQueue", DISPATCH_QUEUE_SERIAL); } return self; }
给Operation添加进度和回调Block
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 /** 给Operation添加进度和回调Block @param progressBlock 进度Block @param completedBlock 回调Block @return 回调字典 */ - (nullable id)addHandlersForProgress:(nullable SDWebImageDownloaderProgressBlock)progressBlock completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock { //初始化一个回调Block列表 SDCallbacksDictionary *callbacks = [NSMutableDictionary new]; //如果progressBlock 和 completedBlock 存在,就把Operation对应的回调和进度Block添加到回调Block列表中 if (progressBlock) callbacks[kProgressCallbackKey] = [progressBlock copy]; if (completedBlock) callbacks[kCompletedCallbackKey] = [completedBlock copy]; LOCK(self.callbacksLock); [self.callbackBlocks addObject:callbacks]; UNLOCK(self.callbacksLock); return callbacks; } //通过key获取回调列表组 - (nullable NSArray<id> *)callbacksForKey:(NSString *)key { LOCK(self.callbacksLock); NSMutableArray<id> *callbacks = [[self.callbackBlocks valueForKey:key] mutableCopy]; UNLOCK(self.callbacksLock); // 我们需要删除[NSNull null],因为每个回调可能并不总是有进度Block [callbacks removeObjectIdenticalTo:[NSNull null]]; return [callbacks copy]; // strip mutability here } //通过token判断是否需要取消Opration - (BOOL)cancel:(nullable id)token { BOOL shouldCancel = NO; LOCK(self.callbacksLock); //通过token移除对象 [self.callbackBlocks removeObjectIdenticalTo:token]; //如果self.callbackBlocks的count为0,则shouldCancel = YES if (self.callbackBlocks.count == 0) { shouldCancel = YES; } UNLOCK(self.callbacksLock); //如果shouldCancel = YES,则执行取消操作 if (shouldCancel) { [self cancel]; } return shouldCancel; }
下载、取消、复位、完成等操作 并行的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 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 - (void)start { //加同步锁 @synchronized (self) { //如果已经被设置为取消状态,就直接重新复位:将Operation从回调Block列表中移除,dataTask设置为nil,并将NSURLSession注销并取消(invalidateAndCancel) if (self.isCancelled) { self.finished = YES; [self reset]; return; } #if SD_UIKIT //查看是否能获取到UIApplication类 Class UIApplicationClass = NSClassFromString(@"UIApplication"); BOOL hasApplication = UIApplicationClass && [UIApplicationClass respondsToSelector:@selector(sharedApplication)]; //如果有UIApplication类并设置了Background模式,则设置一个backgroundTask 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 //获取传入的网络会话对象 NSURLSession *session = self.unownedSession; //如果SDWebImageDownloader传入的session是nil,则手动初始化一个session对象 if (!session) { NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration]; //请求超时时间 15秒 sessionConfig.timeoutIntervalForRequest = 15; session = [NSURLSession sessionWithConfiguration:sessionConfig delegate:self delegateQueue:nil]; self.ownedSession = session; } //如果设置了忽略从NSURLCache中获取缓存的选项 if (self.options & SDWebImageDownloaderIgnoreCachedResponse) { // 获取缓存的数据以供日后检查,不存在就初始化一个URLCache对象 NSURLCache *URLCache = session.configuration.URLCache; if (!URLCache) { URLCache = [NSURLCache sharedURLCache]; } NSCachedURLResponse *cachedResponse; // NSURLCache的 `cachedResponseForRequest:` 不是线程安全的,所以这里加了同步锁。 详情看( https://developer.apple.com/documentation/foundation/nsurlcache#2317483) @synchronized (URLCache) { cachedResponse = [URLCache cachedResponseForRequest:self.request]; } if (cachedResponse) { self.cachedData = cachedResponse.data; } } //生成NSURLSessionTask类对象 self.dataTask = [session dataTaskWithRequest:self.request]; //设置为正在执行状态 self.executing = YES; } //如果NSURLSessionTask类对象存在 if (self.dataTask) { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunguarded-availability" //因为NSURLSessionTask的priority这个属性是iOS8.0以后才有的,所以要判断一下 if ([self.dataTask respondsToSelector:@selector(setPriority:)]) { //根据设置的选项设置优先级 if (self.options & SDWebImageDownloaderHighPriority) { self.dataTask.priority = NSURLSessionTaskPriorityHigh; } else if (self.options & SDWebImageDownloaderLowPriority) { self.dataTask.priority = NSURLSessionTaskPriorityLow; } } #pragma clang diagnostic pop //发送请求,开始执行任务 [self.dataTask resume]; //获取到进度回调代码块并调用 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 { //如果没有获取到NSURLSessionTask类对象,就回调错误并返回 [self callCompletionBlocksWithError:[NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorUnknown userInfo:@{NSLocalizedDescriptionKey : @"Task can't be initialized"}]]; [self done]; return; } #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 } //取消Operation操作。 - (void)cancel { @synchronized (self) { [self cancelInternal]; } } // - (void)cancelInternal { //如果已经结束,返回 if (self.isFinished) return; [super cancel]; //如果存在NSURLSessionTask类对象,就取消,并且异步主队列发送下载开始通知 if (self.dataTask) { [self.dataTask cancel]; __weak typeof(self) weakSelf = self; dispatch_async(dispatch_get_main_queue(), ^{ [[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStopNotification object:weakSelf]; }); // As we cancelled the task, its callback won't be called and thus won't // maintain the isFinished and isExecuting flags. //更新执行状态和结束状态 if (self.isExecuting) self.executing = NO; if (!self.isFinished) self.finished = YES; } //复位 [self reset]; } //完成Operation操作 - (void)done { //更新执行状态和结束状态 self.finished = YES; self.executing = NO; //复位 [self reset]; } //复位 - (void)reset { //删除回调block列表中所有的对象 LOCK(self.callbacksLock); [self.callbackBlocks removeAllObjects]; UNLOCK(self.callbacksLock); //NSURLSessionTask类对象置为nil self.dataTask = 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"]; } //判断Operation是否是并发 - (BOOL)isConcurrent { return YES; }
NSURLSessionDataDelegate 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 //收到服务端响应,再一次请求中只会执行一次 - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler { NSURLSessionResponseDisposition disposition = NSURLSessionResponseAllow; //获取要下载图片的长度 NSInteger expected = (NSInteger)response.expectedContentLength; expected = expected > 0 ? expected : 0; //设置当前expectedSize self.expectedSize = expected; //设置当前response self.response = response; //根据状态代码判断氢气的状态 NSInteger statusCode = [response respondsToSelector:@selector(statusCode)] ? ((NSHTTPURLResponse *)response).statusCode : 200; BOOL valid = statusCode < 400; //'304 Not Modified'是一个特殊的。 如果没有缓存数据,则应将其视为已取消。 //当服务器响应304并且URLCache命中时,URLSession当前行为将返回200状态代码。 但这不是标准行为,我们只是添加一个检查 if (statusCode == 304 && !self.cachedData) { valid = NO; } //如果状态代码是有效的 if (valid) { //遍历进度回调块并触发进度block回调 for (SDWebImageDownloaderProgressBlock progressBlock in [self callbacksForKey:kProgressCallbackKey]) { progressBlock(0, expected, self.request.URL); } } else { // 状态代码无效并标记为已取消。 不要调用`[self.dataTask cancel]` which may mass up URLSession life cycle disposition = NSURLSessionResponseCancel; } //异步主队列发送接受数据通知 __weak typeof(self) weakSelf = self; dispatch_async(dispatch_get_main_queue(), ^{ [[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadReceiveResponseNotification object:weakSelf]; }); //表示允许继续加载 if (completionHandler) { completionHandler(disposition); } } //每次收到数据都会触发, 可能多次调用 - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data { //向可变数据中添加接收到的数据 if (!self.imageData) { self.imageData = [[NSMutableData alloc] initWithCapacity:self.expectedSize]; } [self.imageData appendData:data]; //如果设置了SDWebImageDownloaderProgressiveDownload选项下载(即展示已经下载的部分,并expectedSize返回的图片size大于0) if ((self.options & SDWebImageDownloaderProgressiveDownload) && self.expectedSize > 0) { // 获得图片数据 __block NSData *imageData = [self.imageData copy]; // 获得下载的总字节数 const NSInteger totalSize = imageData.length; // 获得结束的状态 BOOL finished = (totalSize >= self.expectedSize); //如果图片解码器不存在 if (!self.progressiveCoder) { // 因为增量解码需要保留解码后的上下文,我们将为每个下载操作分配一个具有相同类的新实例,以避免冲突 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; } } } // 逐步解码编码器队列中的图像,自定义串行队列异步执行 dispatch_async(self.coderQueue, ^{ //解码生成图片(增量解码图像数据到图像) UIImage *image = [self.progressiveCoder incrementallyDecodedImageWithData:imageData finished:finished]; //如果图片存在,通过URL获取key,再对image进行缩放操作 if (image) { NSString *key = [[SDWebImageManager sharedManager] cacheKeyForURL:self.request.URL]; //image进行缩放操作 image = [self scaledImageForKey:key image:image]; //如果需要压缩图片,就执行压缩操作 if (self.shouldDecompressImages) { image = [[SDWebImageCodersManager sharedInstance] decompressedImageWithImage:image data:&imageData options:@{SDWebImageCoderScaleDownLargeImagesKey: @(NO)}]; } //当`finished`=YES时,不再保留逐行解码图片。 因为它们用于视图渲染但不从下载器选项中获取全部功能。 并且一些编码器实现可能在逐行解码和正常解码之间不一致。 //进行完成回调 [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); } } //当NSURLSessionDataTask弯成接受所有预期数据后会调用这个代理方法,询问代理对象是否将响应存储在缓存中 - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask willCacheResponse:(NSCachedURLResponse *)proposedResponse completionHandler:(void (^)(NSCachedURLResponse *cachedResponse))completionHandler { //获取缓存响应 NSCachedURLResponse *cachedResponse = proposedResponse; //如果设置了缓存选项(SDWebImageDownloaderUseNSURLCache),就回调缓存响应,否则就回调nil,不缓存 if (!(self.options & SDWebImageDownloaderUseNSURLCache)) { // Prevents caching of responses cachedResponse = nil; } //调用完成代码块并传参 if (completionHandler) { completionHandler(cachedResponse); } }
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 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 //当NSURLSessionTask已经完成传输数据时会调用这个代理方法 - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error { //同步锁 @synchronized(self) { //将保存任务对象的属性置为nil 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]; [self done]; } else { //如果有完成回调的代码块 if ([self callbacksForKey:kCompletedCallbackKey].count > 0) { /** * 如果您指定使用`NSURLCache`,那么您在此处获得的响应就是您所需要的。 */ //如果有下载的imageData __block 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 */ 如果设置了忽略缓存响应选项(SDWebImageDownloaderIgnoreCachedResponse),并且缓存数据和下载的图片数据相同 if (self.options & SDWebImageDownloaderIgnoreCachedResponse && [self.cachedData isEqualToData:imageData]) { //回调完成block nil,并调用完成方法 [self callCompletionBlocksWithImage:nil imageData:nil error:nil finished:YES]; [self done]; } else { // 在自定义串行异步执行 dispatch_async(self.coderQueue, ^{ //对下载图片数据进行解码,通过imageData获取图片 UIImage *image = [[SDWebImageCodersManager sharedInstance] decodedImageWithData:imageData]; //获取图片url对应的key NSString *key = [[SDWebImageManager sharedManager] cacheKeyForURL:self.request.URL]; //图片缩放操作 image = [self scaledImageForKey:key image:image]; //生成变量图片是否应该解码, BOOL shouldDecode = YES; // 如果是gif动态图片(图片组)就不需要解码 if (image.images) { shouldDecode = NO; } else { #ifdef SD_WEBP //如果是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)}]; } } //获取处理好的图片尺寸 CGSize imageSize = image.size; //如果图片的宽或者长为0,就回调错误,否则回调完成block if (imageSize.width == 0 || imageSize.height == 0) { [self callCompletionBlocksWithError:[NSError errorWithDomain:SDWebImageErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey : @"Downloaded image has 0 pixels"}]]; } else { //回调完成 [self callCompletionBlocksWithImage:image imageData:imageData error:nil finished:YES]; } //调用完成方法 [self done]; }); } } else { //如果没有下载的图片数据,就回调错误,并执行完成方法 [self callCompletionBlocksWithError:[NSError errorWithDomain:SDWebImageErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey : @"Image data is nil"}]]; [self done]; } } else { //如果没有完成回调代码块,直接执行完成方法 [self done]; } } } //当task接受到身份验证时,会回调这个代理方法 - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler { //设置临时变量保存数据 NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling; __block NSURLCredential *credential = nil; //若验证方式为NSURLAuthenticationMethodServerTrust if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) { //如果没有设置允许不可信的SSL证书,就设置蓄力方式为默认的NSURLSessionAuthChallengePerformDefaultHandling,否则就设置验证模式为指定证书验证,并生成证书 if (!(self.options & SDWebImageDownloaderAllowInvalidSSLCertificates)) { disposition = NSURLSessionAuthChallengePerformDefaultHandling; } else { credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]; disposition = NSURLSessionAuthChallengeUseCredential; } } else { //若验证方式不是NSURLAuthenticationMethodServerTrust if (challenge.previousFailureCount == 0) { //判断是否有证书,如果有就设置证书,验证模式为通过指定证书验证,否则就设置验证模式为不需要验证证书 if (self.credential) { credential = self.credential; disposition = NSURLSessionAuthChallengeUseCredential; } else { disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge; } } else { //如果认证的失败次数设置超过0次,就设置验证模式为不需要验证证书 disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge; } } //调用完成代码并传参 if (completionHandler) { completionHandler(disposition, credential); } }
Helper methods 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 //缩放操作 - (nullable UIImage *)scaledImageForKey:(nullable NSString *)key image:(nullable UIImage *)image { return SDScaledImageForKey(key, image); } //是否允许app后台继续下载图片 - (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); } }); }