打算用几篇文章整理一下 SDWebImage 的源码
源码有点小多, 决定把每个模块分开来整理
这其中包括 : 调度模块、下载模块、缓存模块、解码模块和一些代码整理
调度模块看这里
缓存模块看这里
下载模块看这里
解码模块看这里
整理模块看这里
本文是下载模块
下载模块由 SDWebImageDownloader 管理
SDWebImageDownloader 是以单例存在
作用是创建 NSURLSession, 处理所有下载任务的回调并分发给对应的 SDWebImageDownloaderOperation
也对图片下载管理进行一些全局的配置, 比如:
1).设置最大并发数,下载时间默认15秒,是否压缩图片和下载顺序等。
2).设置operation的请求头信息,负责生成每个图片的下载单位 SDWebImageDownloaderOperation 以及取消operation 等。
3).设置下载的策略SDWebImageDownloaderOptions。
直接看核心方法 :
在调度一文 中的 SDWebImageManager 的 Load 方法中
SDImageCache 在缓存中没有找到图片
那么就会来到 SDWebImageDownloader 的核心方法
去下载图片
- (nullable SDWebImageDownloadToken *)downloadImageWithURL:(nullable NSURL *)url
options:(SDWebImageDownloaderOptions)options
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullableSDWebImageDownloaderCompletedBlock)completedBlock
四个参数很简单
分别是 : 下载链接 下载策略 进度回调 完成回调
代码注解如下
- (nullable SDWebImageDownloadToken *)downloadImageWithURL:(nullable NSURL *)url
options:(SDWebImageDownloaderOptions)options
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock {
// 该URL将用作回调字典的键,因此它不能为空。 如果为零则立即回调,没有图像数据。
if (url == nil) {
if (completedBlock != nil) {
completedBlock(nil, nil, nil, NO);
}
return nil;
}
LOCK(self.operationsLock);
// 先尝试在 URLOperations 中取下载任务, 有一种情况是操作可能被标记为已完成,但未从“self.URLOperations”中删除。
/** 获取的是一个 SDWebImageDownloaderOperation 对象 */
NSOperation<SDWebImageDownloaderOperationInterface> *operation = [self.URLOperations objectForKey:url];
if (!operation || operation.isFinished) {
// crucial moment ! 关键时刻 ! 创建图片下载任务!
operation = [self createDownloaderOperationWithUrl:url options:options];
__weak typeof(self) wself = self;
operation.completionBlock = ^{
__strong typeof(wself) sself = wself;
if (!sself) {
return;
}
LOCK(sself.operationsLock);
[sself.URLOperations removeObjectForKey:url];
UNLOCK(sself.operationsLock);
};
[self.URLOperations setObject:operation forKey:url];
// 将 SDWebImageDownloaderOperation 加入队列开始下载任务
[self.downloadQueue addOperation:operation];
}
UNLOCK(self.operationsLock);
id downloadOperationCancelToken = [operation addHandlersForProgress:progressBlock completed:completedBlock];
// 生成下载 token 用于取消
SDWebImageDownloadToken *token = [SDWebImageDownloadToken new];
token.downloadOperation = operation;
token.url = url;
token.downloadOperationCancelToken = downloadOperationCancelToken;
return token;
}
首先根据下载 URL 去取下载任务
如果下载任务不存在或者已完成, 就创建新的下载任务
把下载任务加入任务队列后执行下载任务
SDWebImageDownloadToken 用于取消任务
创建 SDWebImageDownloaderOperation 下载任务的关键时刻是在这个方法中实现的 :
其中包括一些网络请求的配置
/** 创建下载任务 */
- (NSOperation<SDWebImageDownloaderOperationInterface> *)createDownloaderOperationWithUrl:(nullable NSURL *)url
options:(SDWebImageDownloaderOptions)options {
NSTimeInterval timeoutInterval = self.downloadTimeout;
if (timeoutInterval == 0.0) {
timeoutInterval = 15.0;
}
// 配置
NSURLRequestCachePolicy cachePolicy = options & SDWebImageDownloaderUseNSURLCache ? NSURLRequestUseProtocolCachePolicy : NSURLRequestReloadIgnoringLocalCacheData;
//创建request 设置请求缓存策略 下载时间
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url
cachePolicy:cachePolicy
timeoutInterval:timeoutInterval];
request.HTTPShouldHandleCookies = (options & SDWebImageDownloaderHandleCookies);
request.HTTPShouldUsePipelining = YES;
//设置请求头
if (self.headersFilter) {
request.allHTTPHeaderFields = self.headersFilter(url, [self allHTTPHeaderFields]);
} else {
request.allHTTPHeaderFields = [self allHTTPHeaderFields];
}
// 重头戏 在这里创建 SDWebImageDownloaderOperation 对象
NSOperation<SDWebImageDownloaderOperationInterface> *operation = [[self.operationClass alloc] initWithRequest:request inSession:self.session options:options];
operation.shouldDecompressImages = self.shouldDecompressImages;
if (self.urlCredential) {
operation.credential = self.urlCredential;
} else if (self.username && self.password) {
operation.credential = [NSURLCredential credentialWithUser:self.username password:self.password persistence:NSURLCredentialPersistenceForSession];
}
//下载优先级
if (options & SDWebImageDownloaderHighPriority) {
operation.queuePriority = NSOperationQueuePriorityHigh;
} else if (options & SDWebImageDownloaderLowPriority) {
operation.queuePriority = NSOperationQueuePriorityLow;
}
//设置下载的顺序 是按照队列还是栈
if (self.executionOrder == SDWebImageDownloaderLIFOExecutionOrder) {
// Emulate LIFO execution order by systematically adding new operations as last operation's dependency
[self.lastAddedOperation addDependency:operation];
self.lastAddedOperation = operation;
}
return operation;
}
创建下载任务 SDWebImageDownloaderOperation 的时候, 传入了 self.session 也就是 NSURLSession
SDWebImageDownloaderOperation 拿到这个 NSURLSession 创建 NSURLSessionTask 进行图片请求
self.session 的创建是在 SDWebImageDownloader 单例初始化的时候创建的 :
- (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;
// 初始化 NSOperationQueue
_downloadQueue = [NSOperationQueue new];
// 最大并发出
_downloadQueue.maxConcurrentOperationCount = 6;
// 队列名称
_downloadQueue.name = @"com.hackemist.SDWebImageDownloader";
// 下载url作为key value是具体的下载 operation 用字典来存储,方便cancel等操作
_URLOperations = [NSMutableDictionary new];
// HTTP请求头
SDHTTPHeadersMutableDictionary *headerDictionary = [SDHTTPHeadersMutableDictionary dictionary];
NSString *userAgent = nil;
#if SD_UIKIT
// User-Agent Header; see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.43
userAgent = [NSString stringWithFormat:@"%@/%@ (%@; iOS %@; Scale/%0.2f)", [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleExecutableKey] ?: [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleIdentifierKey], [[NSBundle mainBundle] infoDictionary][@"CFBundleShortVersionString"] ?: [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleVersionKey], [[UIDevice currentDevice] model], [[UIDevice currentDevice] systemVersion], [[UIScreen mainScreen] scale]];
#elif SD_WATCH
// User-Agent Header; see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.43
userAgent = [NSString stringWithFormat:@"%@/%@ (%@; watchOS %@; Scale/%0.2f)", [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleExecutableKey] ?: [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleIdentifierKey], [[NSBundle mainBundle] infoDictionary][@"CFBundleShortVersionString"] ?: [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleVersionKey], [[WKInterfaceDevice currentDevice] model], [[WKInterfaceDevice currentDevice] systemVersion], [[WKInterfaceDevice currentDevice] screenScale]];
#elif SD_MAC
userAgent = [NSString stringWithFormat:@"%@/%@ (Mac OS X %@)", [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleExecutableKey] ?: [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleIdentifierKey], [[NSBundle mainBundle] infoDictionary][@"CFBundleShortVersionString"] ?: [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleVersionKey], [[NSProcessInfo processInfo] operatingSystemVersionString]];
#endif
if (userAgent) {
if (![userAgent canBeConvertedToEncoding:NSASCIIStringEncoding]) {
NSMutableString *mutableUserAgent = [userAgent mutableCopy];
if (CFStringTransform((__bridge CFMutableStringRef)(mutableUserAgent), NULL, (__bridge CFStringRef)@"Any-Latin; Latin-ASCII; [:^ASCII:] Remove", false)) {
userAgent = mutableUserAgent;
}
}
headerDictionary[@"User-Agent"] = userAgent;
}
#ifdef SD_WEBP
headerDictionary[@"Accept"] = @"image/webp,image/*;q=0.8";
#else
headerDictionary[@"Accept"] = @"image/*;q=0.8";
#endif
_HTTPHeaders = headerDictionary;
// 一些信号量
_operationsLock = dispatch_semaphore_create(1);
_headersLock = dispatch_semaphore_create(1);
// 超时时间
_downloadTimeout = 15.0;
// 创建 NSURLSession
[self createNewSessionWithConfiguration:sessionConfiguration];
}
return self;
}
- (void)createNewSessionWithConfiguration:(NSURLSessionConfiguration *)sessionConfiguration {
[self cancelAllDownloads];
if (self.session) {
[self.session invalidateAndCancel];
}
sessionConfiguration.timeoutIntervalForRequest = self.downloadTimeout;
self.session = [NSURLSession sessionWithConfiguration:sessionConfiguration
delegate:self
delegateQueue:nil];
}
也就是说 NSURLSession 的代理的是 SDWebImageDownloader
所以所有图片下载任务的代理回调都由 SDWebImageDownloader 来处理
SDWebImageDownloader 再把每个下载任务的回调分发给对应的 SDWebImageDownloaderOperation
代码如下 :
#pragma mark NSURLSessionDataDelegate
/** 接收到服务器数据, 决定要不要进行 */
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response
completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler {
// 确定运行此任务的操作,并将委托方法传递给它, 确认回调的是哪个任务
NSOperation<SDWebImageDownloaderOperationInterface> *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);
}
}
}
#pragma mark NSURLSessionTaskDelegate
/** task已经完成. */
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
// task 为唯一标识, 去查找对应的 operation
NSOperation<SDWebImageDownloaderOperationInterface> *dataOperation = [self operationWithTask:task];
if ([dataOperation respondsToSelector:@selector(URLSession:task:didCompleteWithError:)]) {
[dataOperation URLSession:session task:task didCompleteWithError:error];
}
}
SDWebImageDownloaderOperation
SDWebImageDownloaderOperation 继承自 NSOperation 是具体的图片下载单位
重写了 NSOperation
的 start
方法
使用 SDWebImageDownloader 的 NSURLSession 创建 NSURLSessionTask 发起图片下载
支持下载取消和后台下载
在下载中及时汇报下载进度
在下载成功后,对图片进行解码,缩放和压缩等操作
SDWebImageDownloaderOperation 的初始化:
- (nonnull instancetype)initWithRequest:(nullable NSURLRequest *)request
inSession:(nullable NSURLSession *)session
options:(SDWebImageDownloaderOptions)options {
if ((self = [super init])) {
// NSURLRequest
_request = [request copy];
// 图片是否可以被压缩属性
_shouldDecompressImages = YES;
// 下载策略
_options = options;
// 将进度 progressBlock 和下载结束 completedBlock 封装成字典 SDCallbacksDictionary
_callbackBlocks = [NSMutableArray new];
// 是否正在执行
_executing = NO;
// 是否完成
_finished = NO;
// 期望data的大小size
_expectedSize = 0;
// SDWebImageDownloader 传入的 NSURLSession
_unownedSession = session;
// 信号量
_callbacksLock = dispatch_semaphore_create(1);
// 解码队列
_coderQueue = dispatch_queue_create("com.hackemist.SDWebImageDownloaderOperationCoderQueue", DISPATCH_QUEUE_SERIAL);
}
return self;
}
start 方法
SDWebImageDownloaderOperation 重写了 NSOperation 的 start 方法
当任务添加到 NSOperationQueue 后会执行该方法,启动下载任务
- (void)start {
//添加同步锁,防止多线程数据竞争
@synchronized (self) {
if (self.isCancelled) {
self.finished = YES;
[self reset];
return;
}
#if SD_UIKIT
//如调用者配置了在后台可以继续下载图片,那么在这里继续下载
Class UIApplicationClass = NSClassFromString(@"UIApplication");
BOOL hasApplication = UIApplicationClass && [UIApplicationClass respondsToSelector:@selector(sharedApplication)];
if (hasApplication && [self shouldContinueWhenAppEntersBackground]) {
__weak __typeof__ (self) wself = self;
UIApplication * app = [UIApplicationClass performSelector:@selector(sharedApplication)];
self.backgroundTaskId = [app beginBackgroundTaskWithExpirationHandler:^{
__strong __typeof (wself) sself = wself;
if (sself) {
[sself cancel];
[app endBackgroundTask:sself.backgroundTaskId];
sself.backgroundTaskId = UIBackgroundTaskInvalid;
}
}];
}
#endif
NSURLSession *session = self.unownedSession;
if (!session) {
// 判断unownedSession是否为了nil,如果是nil则重新创建一个ownedSession
/** SDWebImageDownloaderOperation 在 初始化 的时候已经传入了 session, 使用 sd 自带单例的时候不会遇到为空的情况 */
NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration];
sessionConfig.timeoutIntervalForRequest = 150;
/**
* 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.
*/
session = [NSURLSession sessionWithConfiguration:sessionConfig
delegate:self
delegateQueue:nil];
self.ownedSession = session;
}
// NSURLCache
if (self.options & SDWebImageDownloaderIgnoreCachedResponse) {
// Grab the cached data for later check
NSURLCache *URLCache = session.configuration.URLCache;
if (!URLCache) {
URLCache = [NSURLCache sharedURLCache];
}
NSCachedURLResponse *cachedResponse;
// NSURLCache's `cachedResponseForRequest:` is not thread-safe, see https://developer.apple.com/documentation/foundation/nsurlcache#2317483
@synchronized (URLCache) {
cachedResponse = [URLCache cachedResponseForRequest:self.request];
}
if (cachedResponse) {
self.cachedData = cachedResponse.data;
}
}
// 创建 task
self.dataTask = [session dataTaskWithRequest:self.request];
self.executing = YES;
}
if (self.dataTask) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunguarded-availability"
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 {
// dataTask 创建失败, 回调错误
[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
}
处理图片下载的数据回调
start 方法执行后, 下载任务开启
但是下载回调由 SDWebImageDownloader 接收, 然后分发给对应的 SDWebImageDownloaderOperation
SDWebImageDownloaderOperation 接收到数据的处理 :
省略了其他代理回调方法, 只看下载完成时的回调方法
#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];
}
});
}
// make sure to call `[self done]` to mark operation as finished
if (error) {
[self callCompletionBlocksWithError:error];
[self done];
} else {
/** 图片下载结束的处理 */
if ([self callbacksForKey:kCompletedCallbackKey].count > 0) {
/**
* 如果指定使用`NSURLCache`,那么您在此处获得的响应就是所需要的。
*/
__block NSData *imageData = [self.imageData copy];
if (imageData) {
/ **
如果您指定仅通过`SDWebImageDownloaderIgnoreCachedResponse`使用缓存数据
然后我们应该检查缓存的数据是否等于图像数据
*/
if (self.options & SDWebImageDownloaderIgnoreCachedResponse && [self.cachedData isEqualToData:imageData]) {
// call completion block with nil
[self callCompletionBlocksWithImage:nil imageData:nil error:nil finished:YES];
[self done];
} else {
// decode the image in coder queue
dispatch_async(self.coderQueue, ^{
@autoreleasepool {
// 这一步是 data 转 UIImage => UIImage *image = [[UIImage alloc] initWithData:data];
UIImage *image = [[SDWebImageCodersManager sharedInstance] decodedImageWithData:imageData];
NSString *key = [[SDWebImageManager sharedManager] cacheKeyForURL:self.request.URL];
// 处理一下三倍图和二倍图, 以图片名称中是否有 @2x 和 @3x 为标准
image = [self scaledImageForKey:key image:image];
// 图片是否需要解码
BOOL shouldDecode = YES;
// 不强制解码动画GIF和WebP
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)}];
}
}
CGSize imageSize = image.size;
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];
}
}
}
图片下载结束并解码后回调
下载任务就完成了