1. 开源代码信息

SDWebImage Github 库
SDWebImage Main Repo
SDWebImage 图片Coder插件列表

想必每个接触过iOS开发的开发者都接触过SDWebImage吧,它是一个带有缓存支持的异步图片下载工具。我们常用的UIImageView, *UIButton, MKAnnotationView,这些UI元素都有它为我们提供的专门分类用于异步图片的加载。

特性

  • 为UIImageView, UIButton, MKAnnotationView这些类提供了用于图片加载以及缓存管理的分类。
  • 带有一个异步的图片下载器以及一个缓存过期自动处理的异步内存图片缓存管理。
  • 支持后台图片解压
  • 支持渐进式图片加载功能
  • 可扩展的图像编码器以支持大图片格式,例如WebP
  • 动画图像的全栈解决方案能够做到在CPU和内存之间保持性能平衡
  • 能够支持对下载后图片进行自定义且可组合的转换
  • 可定制的多缓存系统
  • 支持图片加载指示器
  • 支持图片加载过渡动画
  • 同一个URL不会下载多次,虚假的URL不会一直重试
  • 保证主线程不会被阻塞
  • 支持Objective-C 以及 Swift
  • 高性能
  • 支持JPEG, PNG, HEIC图片格式,包含GIF/APNG/HEIC动画格式
  • 支持WebP,以及动画WebP格式
  • 支持可扩张的图片编码器插件,可以通过它来添加更多新的格式。
2. 源码解析
2.1 总入口

我们先来看下给UIImageView设置网络图片的流程的代码,这是我们最常用的一个功能,这部分代码位于:
UIView+WebCache.h其他部分涉及到图片加载的最终都是调用这个方法:

- (void)sd_internalSetImageWithURL:(nullable NSURL *)url
placeholderImage:(nullable UIImage *)placeholder
options:(SDWebImageOptions)options
context:(nullable SDWebImageContext *)context
setImageBlock:(nullable SDSetImageBlock)setImageBlock
progress:(nullable SDImageLoaderProgressBlock)progressBlock
completed:(nullable SDInternalCompletionBlock)completedBlock {

context = [context copy]; // copy to avoid mutable object
NSString *validOperationKey = context[SDWebImageContextSetImageOperationKey];
if (!validOperationKey) {
validOperationKey = NSStringFromClass([self class]);
}
self.sd_latestOperationKey = validOperationKey;

//取消当前运行的同样请求
[self sd_cancelImageLoadOperationWithKey:validOperationKey];

//设置本次加载的URL
self.sd_imageURL = url;

//立刻设置占位图
if (!(options & SDWebImageDelayPlaceholder)) {
dispatch_main_async_safe(^{
//设置占位图
[self sd_setImage:placeholder imageData:nil basedOnClassOrViaCustomSetImageBlock:setImageBlock cacheType:SDImageCacheTypeNone imageURL:url];
});
}

if (url) {
// 重置进度状态
NSProgress *imageProgress = objc_getAssociatedObject(self, @selector(sd_imageProgress));
if (imageProgress) {
imageProgress.totalUnitCount = 0;
imageProgress.completedUnitCount = 0;
}

// 开始图片加载指示器
[self sd_startImageIndicator];
// 图片指示器
id<SDWebImageIndicator> imageIndicator = self.sd_imageIndicator;

//图片管理器
SDWebImageManager *manager = context[SDWebImageContextCustomManager];
if (!manager) {
manager = [SDWebImageManager sharedManager];
}

//更新进度
SDImageLoaderProgressBlock combinedProgressBlock = ^(NSInteger receivedSize, NSInteger expectedSize, NSURL * _Nullable targetURL) {
if (imageProgress) {
imageProgress.totalUnitCount = expectedSize;
imageProgress.completedUnitCount = receivedSize;
}
if ([imageIndicator respondsToSelector:@selector(updateIndicatorProgress:)]) {
double progress = 0;
if (expectedSize != 0) {
progress = (double)receivedSize / expectedSize;
}
progress = MAX(MIN(progress, 1), 0); // 0.0 - 1.0
dispatch_async(dispatch_get_main_queue(), ^{
[imageIndicator updateIndicatorProgress:progress];
});
}
if (progressBlock) {
progressBlock(receivedSize, expectedSize, targetURL);
}
};

//加载图片
@weakify(self);
id <SDWebImageOperation> operation = [manager loadImageWithURL:url
options:options
context:context
progress:combinedProgressBlock
completed:^(UIImage *image, NSData *data, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
@strongify(self);
if (!self) { return; }
// if the progress not been updated, mark it to complete state
if (imageProgress && finished && !error && imageProgress.totalUnitCount == 0 && imageProgress.completedUnitCount == 0) {
imageProgress.totalUnitCount = SDWebImageProgressUnitCountUnknown;
imageProgress.completedUnitCount = SDWebImageProgressUnitCountUnknown;
}

// 加载结束后停止图片指示器
if (finished) {
[self sd_stopImageIndicator];
}
// 是否自动调用complete Block
BOOL shouldCallCompletedBlock = finished || (options & SDWebImageAvoidAutoSetImage);
// 是否不设置Image
BOOL shouldNotSetImage = ((image && (options & SDWebImageAvoidAutoSetImage)) ||
(!image && !(options & SDWebImageDelayPlaceholder)));

SDWebImageNoParamsBlock callCompletedBlockClojure = ^{
if (!self) { return; }
if (!shouldNotSetImage) {
[self sd_setNeedsLayout];
}
// 是否调用completeBlock
if (completedBlock && shouldCallCompletedBlock) {
completedBlock(image, data, error, cacheType, finished, url);
}
};

// 到这里有两种可能,一种是我们获得到了图片,但是SDWebImageAvoidAutoSetImage = 1 或者 我们没拿到图片,并且SDWebImageDelayPlaceholder = 0
if (shouldNotSetImage) {
dispatch_main_async_safe(callCompletedBlockClojure);
return;
}

UIImage *targetImage = nil;
NSData *targetData = nil;
if (image) {
// 获得到额了图片,SDWebImageAvoidAutoSetImage = 0,这是正常流程。
targetImage = image;
targetData = data;
} else if (options & SDWebImageDelayPlaceholder) {
// 没得到图片,但是设置了SDWebImageDelayPlaceholder
targetImage = placeholder;
targetData = nil;
}

// 检查是否使用图片transition
SDWebImageTransition *transition = nil;
if (finished && (options & SDWebImageForceTransition || cacheType == SDImageCacheTypeNone)) {
transition = self.sd_imageTransition;
}
dispatch_main_async_safe(^{
[self sd_setImage:targetImage
imageData:targetData
basedOnClassOrViaCustomSetImageBlock:setImageBlock
transition:transition
cacheType:cacheType
imageURL:imageURL];
callCompletedBlockClojure();
});
}];
//设置当前的operation
[self sd_setImageLoadOperation:operation forKey:validOperationKey];
} else {
//如果url为空则停止加载指示器,并抛出异常。
[self sd_stopImageIndicator];
dispatch_main_async_safe(^{
if (completedBlock) {
NSError *error = [NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorInvalidURL userInfo:@{NSLocalizedDescriptionKey : @"Image url is nil"}];
completedBlock(nil, nil, error, SDImageCacheTypeNone, YES, url);
}
});
}
}

这个方法主要完成了三大块任务:

1. 图片指示器控制
2. 通过图片加载器加载图片
3. 下载完成后给控件设置图片

其他包括缓存,后期图片转换的都在第二部分中处理。这里有些需要注意的细节简单过一下,都是一些option:

SDWebImageDelayPlaceholder :
是否延迟加载占位图,默认一旦开始加载就会显示占位图,如果设置成延迟占位图,那么只有在图片没下载成功的时候设置。
SDWebImageContextCustomManager:
可以外部注入图片管理器,负责图片的缓存控制和图片的下载。默认是没有注入的。
SDWebImageAvoidAutoSetImage
图片下载成功后是否不自动设置图片

2.2 图片加载
- (SDWebImageCombinedOperation *)loadImageWithURL:(nullable NSURL *)url
options:(SDWebImageOptions)options
context:(nullable SDWebImageContext *)context
progress:(nullable SDImageLoaderProgressBlock)progressBlock
completed:(nonnull SDInternalCompletionBlock)completedBlock {
// Invoking this method without a completedBlock is pointless
NSAssert(completedBlock != nil, @"If you mean to prefetch the image, use -[SDWebImagePrefetcher prefetchURLs] instead");

//如果传入的URL是字符串则将其转换为NSURL
if ([url isKindOfClass:NSString.class]) {
url = [NSURL URLWithString:(NSString *)url];
}

// 如果不是NSURL则设置url为空,避免崩溃
if (![url isKindOfClass:NSURL.class]) {
url = nil;
}

SDWebImageCombinedOperation *operation = [SDWebImageCombinedOperation new];
operation.manager = self;

// 判断是否是失败的URL
BOOL isFailedUrl = NO;
if (url) {
SD_LOCK(self.failedURLsLock);
isFailedUrl = [self.failedURLs containsObject:url];
SD_UNLOCK(self.failedURLsLock);
}

//如果地址为空,或者是失败的url则取消请求
if (url.absoluteString.length == 0 || (!(options & SDWebImageRetryFailed) && isFailedUrl)) {
[self callCompletionBlockForOperation:operation completion:completedBlock error:[NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorInvalidURL userInfo:@{NSLocalizedDescriptionKey : @"Image url is nil"}] url:url];
return operation;
}

//将当前的operation添加到self.runningOperations 中表示该请求已经发起
SD_LOCK(self.runningOperationsLock);
[self.runningOperations addObject:operation];
SD_UNLOCK(self.runningOperationsLock);

// 对options和context参数进行预先处理,这些会影响manager加载的最后处理结果
SDWebImageOptionsResult *result = [self processedResultForURL:url options:options context:context];

// 启动图片加载
[self callCacheProcessForOperation:operation url:url options:result.options context:result.context progress:progressBlock completed:completedBlock];

return operation;
}

在图片加载开始的时候会先对传入的url类型进行判断,这里的url可能会有两种类型一种是NSString,一种是NSURL这个阶段会分别对它做一个判断,并最终转换为NSURL,然后判断当前的url是否在failedURLs中,如果在的话会根据SDWebImageRetryFailed的配置来进行选择接下来的处理,如果SDWebImageRetryFailed = 0的话 就表示不对失败的url进行重新尝试,所以这种情况会取消请求,failedURLs会在每次加载失败的时候添加。

紧接着会调用processedResultForURL对options和context参数进行预先处理,这些会影响manager加载的最后处理结果:

- (SDWebImageOptionsResult *)processedResultForURL:(NSURL *)url options:(SDWebImageOptions)options context:(SDWebImageContext *)context {
SDWebImageOptionsResult *result;
SDWebImageMutableContext *mutableContext = [SDWebImageMutableContext dictionary];

// 用于对下载后的图片进行转换后存储到缓存中
if (!context[SDWebImageContextImageTransformer]) {
id<SDImageTransformer> transformer = self.transformer;
[mutableContext setValue:transformer forKey:SDWebImageContextImageTransformer];
}
// 用于将URL转换为它对应的缓存key的组件
if (!context[SDWebImageContextCacheKeyFilter]) {
id<SDWebImageCacheKeyFilter> cacheKeyFilter = self.cacheKeyFilter;
[mutableContext setValue:cacheKeyFilter forKey:SDWebImageContextCacheKeyFilter];
}
// 用于将图片缓存到disk缓存中
if (!context[SDWebImageContextCacheSerializer]) {
id<SDWebImageCacheSerializer> cacheSerializer = self.cacheSerializer;
[mutableContext setValue:cacheSerializer forKey:SDWebImageContextCacheSerializer];
}

if (mutableContext.count > 0) {
if (context) {
[mutableContext addEntriesFromDictionary:context];
}
context = [mutableContext copy];
}

// 对当前的options 以及 context 进行进一步处理
if (self.optionsProcessor) {
result = [self.optionsProcessor processedResultForURL:url options:options context:context];
}
if (!result) {
// 创建默认的SDWebImageOptionsResult
result = [[SDWebImageOptionsResult alloc] initWithOptions:options context:context];
}
return result;
}

它实际上是在context中加入了SDImageTransformerSDWebImageContextCacheKeyFilter,以及SDWebImageContextCacheSerializer,然后通过optionsProcessor 对当前对options 以及 context进行统一处理后返回,这些组件会在加载过程中被提取出来使用。

2.2.1 查询缓存还是从网络下载
- (void)callCacheProcessForOperation:(nonnull SDWebImageCombinedOperation *)operation
url:(nonnull NSURL *)url
options:(SDWebImageOptions)options
context:(nullable SDWebImageContext *)context
progress:(nullable SDImageLoaderProgressBlock)progressBlock
completed:(nullable SDInternalCompletionBlock)completedBlock {

// 检查当前是否需要查询缓存内的图片
BOOL shouldQueryCache = !SD_OPTIONS_CONTAINS(options, SDWebImageFromLoaderOnly);
if (shouldQueryCache) {
//通过url转缓存key的组件,将url转换为对应的缓存key
id<SDWebImageCacheKeyFilter> cacheKeyFilter = context[SDWebImageContextCacheKeyFilter];
NSString *key = [self cacheKeyForURL:url cacheKeyFilter:cacheKeyFilter];
@weakify(operation);
//异步查询,缓存中与当前url对应key的缓存
operation.cacheOperation = [self.imageCache queryImageForKey:key options:options context:context completion:^(UIImage * _Nullable cachedImage, NSData * _Nullable cachedData, SDImageCacheType cacheType) {
@strongify(operation);
//用户取消查找
if (!operation || operation.isCancelled) {
// Image combined operation cancelled by user
[self callCompletionBlockForOperation:operation completion:completedBlock error:[NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorCancelled userInfo:nil] url:url];
[self safelyRemoveOperationFromRunning:operation];
return;
}
// 继续开始下载
[self callDownloadProcessForOperation:operation url:url options:options context:context cachedImage:cachedImage cachedData:cachedData cacheType:cacheType progress:progressBlock completed:completedBlock];
}];
} else {
// 继续开始下载
[self callDownloadProcessForOperation:operation url:url options:options context:context cachedImage:nil cachedData:nil cacheType:SDImageCacheTypeNone progress:progressBlock completed:completedBlock];
}
}

在将下载过程需要的重要组件放置到options和context中后就开始加载图片,这里无外乎两种方式一种是从网络上下载资源,一种是使用本地资源,具体使用哪一种,在加载之前会先根据用户的设置,判断是否只使用缓存的图片,这取决于SDWebImageFromLoaderOnly,如果SDWebImageFromLoaderOnly = 1 会先将URL转换为缓存的key,再使用这个key调用self.imageCache queryImageForKey,异步查询是否有对应的缓存。,如果SDWebImageFromLoaderOnly = 0 则会通过callDownloadProcessForOperation从网络上下载资源。我们接下来分别看下这两个分支的代码:

2.2.1.1 使用缓存资源
- (id<SDWebImageOperation>)queryImageForKey:(NSString *)key options:(SDWebImageOptions)options context:(nullable SDWebImageContext *)context completion:(nullable SDImageCacheQueryCompletionBlock)completionBlock {
//将配置从options搬到cacheOptions
SDImageCacheOptions cacheOptions = 0;
if (options & SDWebImageQueryMemoryData) cacheOptions |= SDImageCacheQueryMemoryData;
if (options & SDWebImageQueryMemoryDataSync) cacheOptions |= SDImageCacheQueryMemoryDataSync;
if (options & SDWebImageQueryDiskDataSync) cacheOptions |= SDImageCacheQueryDiskDataSync;
if (options & SDWebImageScaleDownLargeImages) cacheOptions |= SDImageCacheScaleDownLargeImages;
if (options & SDWebImageAvoidDecodeImage) cacheOptions |= SDImageCacheAvoidDecodeImage;
if (options & SDWebImageDecodeFirstFrameOnly) cacheOptions |= SDImageCacheDecodeFirstFrameOnly;
if (options & SDWebImagePreloadAllFrames) cacheOptions |= SDImageCachePreloadAllFrames;
if (options & SDWebImageMatchAnimatedImageClass) cacheOptions |= SDImageCacheMatchAnimatedImageClass;

return [self queryCacheOperationForKey:key options:cacheOptions context:context done:completionBlock];
}

这里就不多说了就是将缓存相关的配置添加到cacheOptions中调用queryCacheOperationForKey

- (nullable NSOperation *)queryCacheOperationForKey:(nullable NSString *)key options:(SDImageCacheOptions)options context:(nullable SDWebImageContext *)context done:(nullable SDImageCacheQueryCompletionBlock)doneBlock {
//如果key为空则直接返回
if (!key) {
if (doneBlock) {
doneBlock(nil, nil, SDImageCacheTypeNone);
}
return nil;
}
//从context取出图片转换器
id<SDImageTransformer> transformer = context[SDWebImageContextImageTransformer];
if (transformer) {
// 取出我们存储在context中的transformer的transformerKey
NSString *transformerKey = [transformer transformerKey];
key = SDTransformedKeyForKey(key, transformerKey);
}

//首先从内存缓存中查找
UIImage *image = [self imageFromMemoryCacheForKey:key];

if (image) {
// 默认情况下我们会对动画图片整个进行解码,SDImageCacheDecodeFirstFrameOnly表示强迫只解码第一帧图片形成一个静态图
if (options & SDImageCacheDecodeFirstFrameOnly) {
// Ensure static image
Class animatedImageClass = image.class;
if (image.sd_isAnimated || ([animatedImageClass isSubclassOfClass:[UIImage class]] && [animatedImageClass conformsToProtocol:@protocol(SDAnimatedImage)])) {
image = [[UIImage alloc] initWithCGImage:image.CGImage scale:image.scale orientation:image.imageOrientation];
}
} else if (options & SDImageCacheMatchAnimatedImageClass) {
// Check image class matching
Class animatedImageClass = image.class;
Class desiredImageClass = context[SDWebImageContextAnimatedImageClass];
if (desiredImageClass && ![animatedImageClass isSubclassOfClass:desiredImageClass]) {
image = nil;
}
}
}

//是否只使用内存缓存的图片
BOOL shouldQueryMemoryOnly = (image && !(options & SDImageCacheQueryMemoryData));
if (shouldQueryMemoryOnly) {
if (doneBlock) {
doneBlock(image, nil, SDImageCacheTypeMemory);
}
return nil;
}

// 检查磁盘缓存中的资源
NSOperation *operation = [NSOperation new];
// Check whether we need to synchronously query disk
// 1. in-memory cache hit & memoryDataSync
// 2. in-memory cache miss & diskDataSync
BOOL shouldQueryDiskSync = ((image && options & SDImageCacheQueryMemoryDataSync) ||
(!image && options & SDImageCacheQueryDiskDataSync));

void(^queryDiskBlock)(void) = ^{
if (operation.isCancelled) {
if (doneBlock) {
doneBlock(nil, nil, SDImageCacheTypeNone);
}
return;
}

@autoreleasepool {
//从磁盘中查找对应的缓存图片
NSData *diskData = [self diskImageDataBySearchingAllPathsForKey:key];
UIImage *diskImage;
SDImageCacheType cacheType = SDImageCacheTypeNone;
if (image) {
// 使用内存缓存中的图片
// the image is from in-memory cache, but need image data
diskImage = image;
cacheType = SDImageCacheTypeMemory;
} else if (diskData) {
//使用磁盘缓存中的图片
cacheType = SDImageCacheTypeDisk;
// decode image data only if in-memory cache missed
// 在内存缓存没有命中的时候解压从磁盘中获取到的文件
diskImage = [self diskImageForKey:key data:diskData options:options context:context];
if (diskImage && self.config.shouldCacheImagesInMemory) {
//是否需要存储到内存缓存中
NSUInteger cost = diskImage.sd_memoryCost;
[self.memoryCache setObject:diskImage forKey:key cost:cost];
}
}

//交付数据
if (doneBlock) {
if (shouldQueryDiskSync) {
doneBlock(diskImage, diskData, cacheType);
} else {
dispatch_async(dispatch_get_main_queue(), ^{
doneBlock(diskImage, diskData, cacheType);
});
}
}
}
};

// Query in ioQueue to keep IO-safe
// 开始查询,这里分同步和异步查询两种
if (shouldQueryDiskSync) {
dispatch_sync(self.ioQueue, queryDiskBlock);
} else {
dispatch_async(self.ioQueue, queryDiskBlock);
}

return operation;
}

queryCacheOperationForKey中做的工作和其他图片加载库类似,只不过在细节会有点差异:

  1. 按照key的规则构建出缓存key
  2. 优先从缓存中查找缓存,如果只使用内存缓存的则在这个步骤不论是否能够在内存中找到缓存,都通过block返回结果。
  3. 如果在内存缓存中没有找到对应的图片缓存,则继续查找磁盘缓存,如果在磁盘中找到了,再看是否需要同步到内存缓存。
  4. 最后交付数据到应用层

下面将针对上面几点展开介绍:

  • 按照key的规则构建出缓存key

SDWebImage中的缓存key和转换器是有关联,所以我们在构建key的时候需要将转换器从context中取出。

id<SDImageTransformer> transformer = context[SDWebImageContextImageTransformer];
if (transformer) {
// 取出我们存储在context中的transformer的transformerKey
NSString *transformerKey = [transformer transformerKey];
key = SDTransformedKeyForKey(key, transformerKey);
}

缓存key的具体构建过程在SDTransformedKeyForKey方法中,这里会在原先的缓存key后面添加不同的转换器信息,用于区分同一个url 对应不同转换器的情况,比如我们有一个图片为image.png,它经过flip(YES,NO) 也就是水平翻转,再经过旋转45度处理,这样最终生成的key就是image-SDImageFlippingTransformer(1,0)-SDImageRotationTransformer(0.78539816339,1).png:

NSString * _Nullable SDTransformedKeyForKey(NSString * _Nullable key, NSString * _Nonnull transformerKey) {
if (!key || !transformerKey) {
return nil;
}
// Find the file extension
NSURL *keyURL = [NSURL URLWithString:key];
NSString *ext = keyURL ? keyURL.pathExtension : key.pathExtension;
if (ext.length > 0) {
// For non-file URL
if (keyURL && !keyURL.isFileURL) {
// keep anything except path (like URL query)
NSURLComponents *component = [NSURLComponents componentsWithURL:keyURL resolvingAgainstBaseURL:NO];
component.path = [[[component.path.stringByDeletingPathExtension stringByAppendingString:SDImageTransformerKeySeparator] stringByAppendingString:transformerKey] stringByAppendingPathExtension:ext];
return component.URL.absoluteString;
} else {
// file URL
return [[[key.stringByDeletingPathExtension stringByAppendingString:SDImageTransformerKeySeparator] stringByAppendingString:transformerKey] stringByAppendingPathExtension:ext];
}
} else {
return [[key stringByAppendingString:SDImageTransformerKeySeparator] stringByAppendingString:O];
}
}

在拿到key后key后我们会先从内存图片缓存中去查找对与当前key匹配的图片。

//首先从内存缓存中查找
UIImage *image = [self imageFromMemoryCacheForKey:key];

--->

- (nullable UIImage *)imageFromMemoryCacheForKey:(nullable NSString *)key {
return [self.memoryCache objectForKey:key];
}

如果指定只使用内存中的缓存图片并且内存中有找到对应的图片就直接返回:

//是否只使用内存缓存的图片
BOOL shouldQueryMemoryOnly = (image && !(options & SDImageCacheQueryMemoryData));
if (shouldQueryMemoryOnly) {
if (doneBlock) {
doneBlock(image, nil, SDImageCacheTypeMemory);
}
return nil;
}

如果shouldQueryMemoryOnly = NO,也就是不单单使用内存的图片缓存,就会调用diskImageDataBySearchingAllPathsForKey在磁盘中查找对应的缓存图片,如同内存中有则优先使用内存的,其实这里有个需要优化的地方,如果image不为空的情况下就不需要调用diskImageDataBySearchingAllPathsForKey,如果内存没有,那么会将从磁盘缓存中获取到的缓存数据,经过解码后存放到内存缓存,供下次使用。

BOOL shouldQueryDiskSync = ((image && options & SDImageCacheQueryMemoryDataSync) ||
(!image && options & SDImageCacheQueryDiskDataSync));

void(^queryDiskBlock)(void) = ^{
if (operation.isCancelled) {
if (doneBlock) {
doneBlock(nil, nil, SDImageCacheTypeNone);
}
return;
}
@autoreleasepool {
//从磁盘中查找对应的缓存图片
NSData *diskData = [self diskImageDataBySearchingAllPathsForKey:key];
UIImage *diskImage;
SDImageCacheType cacheType = SDImageCacheTypeNone;
if (image) {
// 使用内存缓存中的图片
diskImage = image;
cacheType = SDImageCacheTypeMemory;
} else if (diskData) {
//使用磁盘缓存中的图片
cacheType = SDImageCacheTypeDisk;
// 在内存缓存没有命中的时候解压从磁盘中获取到的文件
diskImage = [self diskImageForKey:key data:diskData options:options context:context];
if (diskImage && self.config.shouldCacheImagesInMemory) {
//是否需要存储到内存缓存中
NSUInteger cost = diskImage.sd_memoryCost;
[self.memoryCache setObject:diskImage forKey:key cost:cost];
}
}
//交付数据
if (doneBlock) {
if (shouldQueryDiskSync) {
doneBlock(diskImage, diskData, cacheType);
} else {
dispatch_async(dispatch_get_main_queue(), ^{
doneBlock(diskImage, diskData, cacheType);
});
}
}
}
};
// 开始查询,这里分同步和异步查询两种
if (shouldQueryDiskSync) {
dispatch_sync(self.ioQueue, queryDiskBlock);
} else {
dispatch_async(self.ioQueue, queryDiskBlock);
}
2.2.1.2 使用网络资源

上面小节我们了解了从缓存中加载图片的流程,接下来我们看下从网络上对图片进行加载的流程:

在SDWebImage默认情况下使用SDWebImageDownloader来完成下载任务,我们在分析下载流程之前我们先看下SDWebImageDownloader数据流是怎样的:

  • SDWebImageDownloader初始化阶段

在SDWebImageDownloader 第一次调用的时候会调用initialize方法

+ (void)initialize {
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)init {
return [self initWithConfig:SDWebImageDownloaderConfig.defaultDownloaderConfig];
}
- (instancetype)initWithConfig:(SDWebImageDownloaderConfig *)config {
self = [super init];
if (self) {
if (!config) {
config = SDWebImageDownloaderConfig.defaultDownloaderConfig;
}
_config = [config copy];
//指定并行下载数目
[_config addObserver:self forKeyPath:NSStringFromSelector(@selector(maxConcurrentDownloads)) options:0 context:SDWebImageDownloaderContext];

//创建下载队列_downloadQueue
_downloadQueue = [NSOperationQueue new];
_downloadQueue.maxConcurrentOperationCount = _config.maxConcurrentDownloads;
_downloadQueue.name = @"com.hackemist.SDWebImageDownloader";

//创建用于管理NSOperation的字典,key为url,value为对应的NSOperation
_URLOperations = [NSMutableDictionary new];
NSMutableDictionary<NSString *, NSString *> *headerDictionary = [NSMutableDictionary dictionary];
NSString *userAgent = nil;
// 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]];
//UA设置
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;
}
//Accept context设置
headerDictionary[@"Accept"] = @"image/*,*/*;q=0.8";
_HTTPHeaders = headerDictionary;
_HTTPHeadersLock = dispatch_semaphore_create(1);
_operationsLock = dispatch_semaphore_create(1);
NSURLSessionConfiguration *sessionConfiguration = _config.sessionConfiguration;
if (!sessionConfiguration) {
sessionConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration];
}
/**
* 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.
*/
//创建NSURLSession
_session = [NSURLSession sessionWithConfiguration:sessionConfiguration
delegate:self
delegateQueue:nil];
}
return self;
}

在初始化方法中主要负责重要组件的创建****_downloadQueue_URLOperations_session****。

  • SDWebImageDownloader下载流程

SDWebImage的下载是从callDownloadProcessForOperation开始的:

- (void)callDownloadProcessForOperation:(nonnull SDWebImageCombinedOperation *)operation
url:(nonnull NSURL *)url
options:(SDWebImageOptions)options
context:(SDWebImageContext *)context
cachedImage:(nullable UIImage *)cachedImage
cachedData:(nullable NSData *)cachedData
cacheType:(SDImageCacheType)cacheType
progress:(nullable SDImageLoaderProgressBlock)progressBlock
completed:(nullable SDInternalCompletionBlock)completedBlock {

BOOL shouldDownload = !SD_OPTIONS_CONTAINS(options, SDWebImageFromCacheOnly);
shouldDownload &= (!cachedImage || options & SDWebImageRefreshCached);
shouldDownload &= (![self.delegate respondsToSelector:@selector(imageManager:shouldDownloadImageForURL:)] || [self.delegate imageManager:self shouldDownloadImageForURL:url]);
shouldDownload &= [self.imageLoader canRequestImageForURL:url];

if (shouldDownload) {
//在需要强制刷新缓存的情况下先调用callCompletionBlockForOperation将缓存图片交给上层,然后继续开始下载
if (cachedImage && options & SDWebImageRefreshCached) {
// If image was found in the cache but SDWebImageRefreshCached is provided, notify about the cached image
// AND try to re-download it in order to let a chance to NSURLCache to refresh it from server.
[self callCompletionBlockForOperation:operation completion:completedBlock image:cachedImage data:cachedData error:nil cacheType:cacheType finished:YES url:url];
// Pass the cached image to the image loader. The image loader should check whether the remote image is equal to the cached image.
SDWebImageMutableContext *mutableContext;
if (context) {
mutableContext = [context mutableCopy];
} else {
mutableContext = [NSMutableDictionary dictionary];
}
mutableContext[SDWebImageContextLoaderCachedImage] = cachedImage;
context = [mutableContext copy];
}

@weakify(operation);
// 开始发起图片加载的请求
operation.loaderOperation = [self.imageLoader requestImageWithURL:url options:options context:context progress:progressBlock completed:^(UIImage *downloadedImage, NSData *downloadedData, NSError *error, BOOL finished) {
@strongify(operation);
//用户取消请求
if (!operation || operation.isCancelled) {
[self callCompletionBlockForOperation:operation completion:completedBlock error:[NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorCancelled userInfo:nil] url:url];
} else if (cachedImage && options & SDWebImageRefreshCached && [error.domain isEqualToString:SDWebImageErrorDomain] && error.code == SDWebImageErrorCacheNotModified) {
// 强制刷新缓存的时候,在前面先将缓存图片通过completeBlock 将数据传递给上层,到这里将数据加载完毕后,不做任何操作
} else if ([error.domain isEqualToString:SDWebImageErrorDomain] && error.code == SDWebImageErrorCancelled) {
// 将错误交给上层
[self callCompletionBlockForOperation:operation completion:completedBlock error:error url:url];
} else if (error) {
[self callCompletionBlockForOperation:operation completion:completedBlock error:error url:url];
//是否需要将错误的URL添加到failedURLs
BOOL shouldBlockFailedURL = [self shouldBlockFailedURLWithURL:url error:error];

if (shouldBlockFailedURL) {
SD_LOCK(self.failedURLsLock);
[self.failedURLs addObject:url];
SD_UNLOCK(self.failedURLsLock);
}
} else {
// 成功
//如果SDWebImageRetryFailed = 1 表示错误的url需要重试,这时候就要将当前的url从failedURLs移除
if ((options & SDWebImageRetryFailed)) {
SD_LOCK(self.failedURLsLock);
[self.failedURLs removeObject:url];
SD_UNLOCK(self.failedURLsLock);
}

[self callStoreCacheProcessForOperation:operation url:url options:options context:context downloadedImage:downloadedImage downloadedData:downloadedData finished:finished progress:progressBlock completed:completedBlock];
}
if (finished) {
//加载成功后将操作从Running队列中移除
[self safelyRemoveOperationFromRunning:operation];
}
}];
} else if (cachedImage) {
//使用缓存的情况下将缓存数据传递到上层,并将请求从Running队列中移除
[self callCompletionBlockForOperation:operation completion:completedBlock image:cachedImage data:cachedData error:nil cacheType:cacheType finished:YES url:url];
[self safelyRemoveOperationFromRunning:operation];
} else {
//缓存没有图片,并且不允许下载
[self callCompletionBlockForOperation:operation completion:completedBlock image:nil data:nil error:nil cacheType:SDImageCacheTypeNone finished:YES url:url];
[self safelyRemoveOperationFromRunning:operation];
}
}

是否需要从网络上下载图片需要根据如下条件进行判断:

  1. SDWebImageFromCacheOnly = 0,表示不单单只允许使用缓存的数据
  2. 上一步没有找到缓存cachedImage
  3. SDWebImageRefreshCached = 1 强制刷新缓存
  4. imageManager:shouldDownloadImageForURL:决定当图片在缓存内没有找到的时候是否需要下载
  5. [self.imageLoader canRequestImageForURL:url] 决定是否能够下载,它会在每次imageloader请求到来的时候,对URL进行检查,
    如果返回YES,则会开始调用requestImageWithURL发起请求,否则将会将当前的图片加载标记为加载失败。

这里需要强调的是如果是需要强制刷新的时候会先将缓存图片通过block上传给业务层,但是会继续下载任务。

下载是调用****self.imageLoader requestImageWithURL:****来执行的,我们来看下:

- (id<SDWebImageOperation>)requestImageWithURL:(NSURL *)url options:(SDWebImageOptions)options context:(SDWebImageContext *)context progress:(SDImageLoaderProgressBlock)progressBlock completed:(SDImageLoaderCompletedBlock)completedBlock {
if (!url) {
return nil;
}
NSArray<id<SDImageLoader>> *loaders = self.loaders;
for (id<SDImageLoader> loader in loaders.reverseObjectEnumerator) {
if ([loader canRequestImageForURL:url]) {
return [loader requestImageWithURL:url options:options context:context progress:progressBlock completed:completedBlock];
}
}
return nil;
}

这里会遍历每个注册的SDImageLoader,对每个loader调用canRequestImageForURL 来判断当前loader能否对当前url请求做处理,如果能就进行请求,这种插件化处理是很多通用库采用的套路。我们看下SDImageLoadersManager

- (id<SDWebImageOperation>)requestImageWithURL:(NSURL *)url options:(SDWebImageOptions)options context:(SDWebImageContext *)context progress:(SDImageLoaderProgressBlock)progressBlock completed:(SDImageLoaderCompletedBlock)completedBlock {
UIImage *cachedImage = context[SDWebImageContextLoaderCachedImage];

SDWebImageDownloaderOptions downloaderOptions = 0;
if (options & SDWebImageLowPriority) downloaderOptions |= SDWebImageDownloaderLowPriority;
if (options & SDWebImageProgressiveLoad) downloaderOptions |= SDWebImageDownloaderProgressiveLoad;
if (options & SDWebImageRefreshCached) downloaderOptions |= SDWebImageDownloaderUseNSURLCache;
if (options & SDWebImageContinueInBackground) downloaderOptions |= SDWebImageDownloaderContinueInBackground;
if (options & SDWebImageHandleCookies) downloaderOptions |= SDWebImageDownloaderHandleCookies;
if (options & SDWebImageAllowInvalidSSLCertificates) downloaderOptions |= SDWebImageDownloaderAllowInvalidSSLCertificates;
if (options & SDWebImageHighPriority) downloaderOptions |= SDWebImageDownloaderHighPriority;
if (options & SDWebImageScaleDownLargeImages) downloaderOptions |= SDWebImageDownloaderScaleDownLargeImages;
if (options & SDWebImageAvoidDecodeImage) downloaderOptions |= SDWebImageDownloaderAvoidDecodeImage;
if (options & SDWebImageDecodeFirstFrameOnly) downloaderOptions |= SDWebImageDownloaderDecodeFirstFrameOnly;
if (options & SDWebImagePreloadAllFrames) downloaderOptions |= SDWebImageDownloaderPreloadAllFrames;
if (options & SDWebImageMatchAnimatedImageClass) downloaderOptions |= SDWebImageDownloaderMatchAnimatedImageClass;

if (cachedImage && options & SDWebImageRefreshCached) {
// force progressive off if image already cached but forced refreshing
downloaderOptions &= ~SDWebImageDownloaderProgressiveLoad;
// ignore image read from NSURLCache if image if cached but force refreshing
downloaderOptions |= SDWebImageDownloaderIgnoreCachedResponse;
}

return [self downloadImageWithURL:url options:downloaderOptions context:context progress:progressBlock completed:completedBlock];
}

和查找缓存一样在执行真正的下载任务之前,会先重建一份options,然后再开始真正的请求:

- (nullable SDWebImageDownloadToken *)downloadImageWithURL:(nullable NSURL *)url
options:(SDWebImageDownloaderOptions)options
context:(nullable SDWebImageContext *)context
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock {
//..........
SD_LOCK(self.operationsLock);
id downloadOperationCancelToken;
// 通过当前的url去self.URLOperations 中去取NSOperation
NSOperation<SDWebImageDownloaderOperation> *operation = [self.URLOperations objectForKey:url];
// 如果当前url对应的operation为空,并且operation.isFinished = YES 或者 operation.isCancelled = YES,就调用createDownloaderOperationWithUrl新建一个NSOperation。
// operation.isFinished = YES 或者 operation.isCancelled = YES 会发生在operation被取消或者被标记为结束,但是并没有将它从self.URLOperations移除
if (!operation || operation.isFinished || operation.isCancelled) {
operation = [self createDownloaderOperationWithUrl:url options:options context:context];
// .......
//operation 执行结束后会将当前url对应的operation从URLOperations中移除
@weakify(self);
operation.completionBlock = ^{
@strongify(self);
//......
SD_LOCK(self.operationsLock);
[self.URLOperations removeObjectForKey:url];
SD_UNLOCK(self.operationsLock);
};
//添加到self.URLOperations中
self.URLOperations[url] = operation;
// 将operation 添加到下载队列
[self.downloadQueue addOperation:operation];
//为当前的operation添加下载进度block以及下载完成的block
downloadOperationCancelToken = [operation addHandlersForProgress:progressBlock completed:completedBlock];
} else {
// So we lock the operation here, and in `SDWebImageDownloaderOperation`, we use `@synchonzied (self)`, to ensure the thread safe between these two classes.
// 走到这里表示之前有对应的下载了,我们可以复用已经存在的下载,并添加progressBlock 以及completedBlock,这样在原先的下载任务完成后也会通知到当前的请求
@synchronized (operation) {
downloadOperationCancelToken = [operation addHandlersForProgress:progressBlock completed:completedBlock];
}
if (!operation.isExecuting) {
if (options & SDWebImageDownloaderHighPriority) {
operation.queuePriority = NSOperationQueuePriorityHigh;
} else if (options & SDWebImageDownloaderLowPriority) {
operation.queuePriority = NSOperationQueuePriorityLow;
} else {
operation.queuePriority = NSOperationQueuePriorityNormal;
}
}
}
SD_UNLOCK(self.operationsLock);
//这里返回的是一个SDWebImageDownloadToken,后面可以通过它来取消这个下载任务
SDWebImageDownloadToken *token = [[SDWebImageDownloadToken alloc] initWithDownloadOperation:operation];
token.url = url;
token.request = operation.request;
token.downloadOperationCancelToken = downloadOperationCancelToken;
return token;
}

下载请求是基于NSOperation的,每个url对应一个NSOperation,这些NSOperation放在self.URLOperations 中统一管理,每次发起一个未存在的图片请求的时候就会新建一个NSOperation并添加到self.URLOperations,一旦完成下载这个url对应的NSOperation就会从self.URLOperations中移除。如果发起一个已经存在的url请求,那么就会复用当前已经开始的NSOperation,将当前的progressBlockcompletedBlock 添加到 已经存在的NSOperation中,这样在原先的下载任务完成后也会通知到当前的请求响应的对应block。

我们接下来看下NSOperation的创建方法createDownloaderOperationWithUrl,这里主要是构建出NSRequest并通过请求修改器,对请求进行拦截修改,然后将返回体修改器,解密器以及Request 添加到NSOperation中。

- (nullable NSOperation<SDWebImageDownloaderOperation> *)createDownloaderOperationWithUrl:(nonnull NSURL *)url
options:(SDWebImageDownloaderOptions)options
context:(nullable SDWebImageContext *)context {

//配置网络超时时间,缓存策略等
NSTimeInterval timeoutInterval = self.config.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
NSURLRequestCachePolicy cachePolicy = options & SDWebImageDownloaderUseNSURLCache ? NSURLRequestUseProtocolCachePolicy : NSURLRequestReloadIgnoringLocalCacheData;
NSMutableURLRequest *mutableRequest = [[NSMutableURLRequest alloc] initWithURL:url cachePolicy:cachePolicy timeoutInterval:timeoutInterval];
mutableRequest.HTTPShouldHandleCookies = SD_OPTIONS_CONTAINS(options, SDWebImageDownloaderHandleCookies);
mutableRequest.HTTPShouldUsePipelining = YES;
SD_LOCK(self.HTTPHeadersLock);
mutableRequest.allHTTPHeaderFields = self.HTTPHeaders;
SD_UNLOCK(self.HTTPHeadersLock);

// 取出context
SDWebImageMutableContext *mutableContext;
if (context) {
mutableContext = [context mutableCopy];
} else {
mutableContext = [NSMutableDictionary dictionary];
}

// 从context中取出requestModifier,它会对当前的URL进行修改。
id<SDWebImageDownloaderRequestModifier> requestModifier;
if ([context valueForKey:SDWebImageContextDownloadRequestModifier]) {
requestModifier = [context valueForKey:SDWebImageContextDownloadRequestModifier];
} else {
requestModifier = self.requestModifier;
}

NSURLRequest *request;
if (requestModifier) {
NSURLRequest *modifiedRequest = [requestModifier modifiedRequestWithRequest:[mutableRequest copy]];
// If modified request is nil, early return
if (!modifiedRequest) {
return nil;
} else {
request = [modifiedRequest copy];
}
} else {
request = [mutableRequest copy];
}


// Response 修改器件
id<SDWebImageDownloaderResponseModifier> responseModifier;
if ([context valueForKey:SDWebImageContextDownloadResponseModifier]) {
responseModifier = [context valueForKey:SDWebImageContextDownloadResponseModifier];
} else {
responseModifier = self.responseModifier;
}
if (responseModifier) {
mutableContext[SDWebImageContextDownloadResponseModifier] = responseModifier;
}
// 解密器
id<SDWebImageDownloaderDecryptor> decryptor;
if ([context valueForKey:SDWebImageContextDownloadDecryptor]) {
decryptor = [context valueForKey:SDWebImageContextDownloadDecryptor];
} else {
decryptor = self.decryptor;
}
if (decryptor) {
mutableContext[SDWebImageContextDownloadDecryptor] = decryptor;
}
context = [mutableContext copy];


// 取出指定的NSOperation class
Class operationClass = self.config.operationClass;
if (operationClass && [operationClass isSubclassOfClass:[NSOperation class]] && [operationClass conformsToProtocol:@protocol(SDWebImageDownloaderOperation)]) {
// Custom operation class
} else {
operationClass = [SDWebImageDownloaderOperation class];
}
//将处理过的request,以及 Response 修改器件,解密器放置到NSOperation
NSOperation<SDWebImageDownloaderOperation> *operation = [[operationClass alloc] initWithRequest:request inSession:self.session options:options context:context];

//设置认证信息
if ([operation respondsToSelector:@selector(setCredential:)]) {
if (self.config.urlCredential) {
operation.credential = self.config.urlCredential;
} else if (self.config.username && self.config.password) {
operation.credential = [NSURLCredential credentialWithUser:self.config.username password:self.config.password persistence:NSURLCredentialPersistenceForSession];
}
}

//设置最小的进度
if ([operation respondsToSelector:@selector(setMinimumProgressInterval:)]) {
operation.minimumProgressInterval = MIN(MAX(self.config.minimumProgressInterval, 0), 1);
}

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

if (self.config.executionOrder == SDWebImageDownloaderLIFOExecutionOrder) {
// Emulate LIFO execution order by systematically, each previous adding operation can dependency the new operation
// This can gurantee the new operation to be execulated firstly, even if when some operations finished, meanwhile you appending new operations
// Just make last added operation dependents new operation can not solve this problem. See test case #test15DownloaderLIFOExecutionOrder
for (NSOperation *pendingOperation in self.downloadQueue.operations) {
[pendingOperation addDependency:operation];
}
}
return operation;
}

看到这里大家可能会有疑问,上面只是构建NSOperation然后添加到downloadQueue中,那么什么时候开始呢?我们看到了NSOperation创建过程如下:

NSOperation<SDWebImageDownloaderOperation> *operation = [[operationClass alloc] initWithRequest:request inSession:self.session options:options context:context];

这里持有了NSSession,我们知道网络数据的下载都是依靠NSSession的,所以下载的触发应该也是在NSOperation中。所以我们带着这个问题来看下SDWebImageDownloaderOperation从初始化方法中我们可以看到它包含很多重要的组件,具体的见下面注释。

- (nonnull instancetype)initWithRequest:(nullable NSURLRequest *)request
inSession:(nullable NSURLSession *)session
options:(SDWebImageDownloaderOptions)options
context:(nullable SDWebImageContext *)context {
if ((self = [super init])) {
_request = [request copy]; //当前下载请求NSURLRequest
_options = options; //下载选项
_context = [context copy]; //包含一系列组件的上下文
_callbackBlocks = [NSMutableArray new];//进度通知block和下载完成处理block
_responseModifier = context[SDWebImageContextDownloadResponseModifier];//返回内容修改器
_decryptor = context[SDWebImageContextDownloadDecryptor];//解码器
_executing = NO; //当前状态
_finished = NO; //当前状态
_expectedSize = 0;
_unownedSession = session; //NSSession
_coderQueue = dispatch_queue_create("com.hackemist.SDWebImageDownloaderOperationCoderQueue", DISPATCH_QUEUE_SERIAL);//解码队列
_backgroundTaskId = UIBackgroundTaskInvalid;//后台id
}
return self;
}

接下来我们带着大家来解决如下几个问题:

  1. SDWebImageDownloaderOperation是如何使用NSSession发起下载请求的?
  2. 请求后通过代理返回的数据怎么传到SDWebImageDownloaderOperation中?在SDWebImageDownloaderOperation又是怎么做处理的。
  3. 图片下载后是怎么通知给上层的,下载进度又是如何传递的?

我们先来看下第一个问题:

  • SDWebImageDownloaderOperation是如何使用NSSession发起下载请求的?

这个比较好回答,我们知道NSOperation运行会调用它的start方法,所以我们要回答这个问题也应该从这个方法入手:

- (void)start {
@synchronized (self) {

//开始之前就已经被取消了
if (self.isCancelled) {
self.finished = YES;
// Operation cancelled by user before sending the request
[self callCompletionBlocksWithError:[NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorCancelled userInfo:nil]];
[self reset];
return;
}
//退出后台后会停止该下载任务
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:^{
[wself cancel];
}];
}
//构建session
NSURLSession *session = self.unownedSession;
//......
//通过session 以及request 创建出一个 dataTask
self.dataTask = [session dataTaskWithRequest:self.request];
self.executing = YES;
}

if (self.dataTask) {
if (self.options & SDWebImageDownloaderHighPriority) {
self.dataTask.priority = NSURLSessionTaskPriorityHigh;
} else if (self.options & SDWebImageDownloaderLowPriority) {
self.dataTask.priority = NSURLSessionTaskPriorityLow;
}
//启动下载任务
[self.dataTask resume];
for (SDWebImageDownloaderProgressBlock progressBlock in [self callbacksForKey:kProgressCallbackKey]) {
progressBlock(0, NSURLResponseUnknownLength, self.request.URL);
}
//发出启动下载通知
__block typeof(self) strongSelf = self;
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStartNotification object:strongSelf];
});
} else {
//.....
}
}

整个过程还是拿传入的request使用NSSession创建出dataTask,然后调用dataTask的resume方法启动下载。下载后数据会从相应的代理方法中获得,但是代理是SDWebImageDownloader,如何将它传给SDWebImageDownloaderOperation呢?我们接下来就来回答这第二个问题,如果看过AFNetWorking源码的对这个流程会比较熟悉,大致相同。

  • *请求后通过代理返回的数据怎么传到SDWebImageDownloaderOperation中?在SDWebImageDownloaderOperation又是怎么做处理的

我们在delegate中拿到的是task,所以我们要通过task找到它所属的NSOperation:

- (NSOperation<SDWebImageDownloaderOperation> *)operationWithTask:(NSURLSessionTask *)task {
NSOperation<SDWebImageDownloaderOperation> *returnOperation = nil;
for (NSOperation<SDWebImageDownloaderOperation> *operation in self.downloadQueue.operations) {
if ([operation respondsToSelector:@selector(dataTask)]) {
if (operation.dataTask.taskIdentifier == task.taskIdentifier) {
returnOperation = operation;
break;
}
}
}
return returnOperation;
}

嗯,就是通过遍历NSOperation拿当前的task的taskIdentifier与self.downloadQueue.operations中task的taskIdentifier进行匹配,找到所属的NSOperation。

我们下下面的一个代理方法,它就是通过operationWithTask找到对应的NSOperation,然后再调用NSOperation中相对应的方法处理:

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

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

我们接下来看下具体代理的处理:

收到服务端回复的时候会调用didReceiveResponse代理方法,这里主要是通过使用返回体修改器对返回的请求进行处理,然后再进行状态码进行校验。具体见如下代码注释:

- (void)URLSession:(NSURLSession *)session
dataTask:(NSURLSessionDataTask *)dataTask
didReceiveResponse:(NSURLResponse *)response
completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler {
NSURLSessionResponseDisposition disposition = NSURLSessionResponseAllow;

// Check response modifier, if return nil, will marked as cancelled.
BOOL valid = YES;
// 使用返回体修改器对返回的请求进行处理
if (self.responseModifier && response) {
response = [self.responseModifier modifiedResponseWithResponse:response];
if (!response) {
valid = NO;
self.responseError = [NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorInvalidDownloadResponse userInfo:nil];
}
}

NSInteger expected = (NSInteger)response.expectedContentLength;
expected = expected > 0 ? expected : 0;
self.expectedSize = expected;
self.response = response;

//状态码检查
NSInteger statusCode = [response respondsToSelector:@selector(statusCode)] ? ((NSHTTPURLResponse *)response).statusCode : 200;
// Status code should between [200,400)
BOOL statusCodeValid = statusCode >= 200 && statusCode < 400;
if (!statusCodeValid) {
valid = NO;
self.responseError = [NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorInvalidDownloadStatusCode userInfo:@{SDWebImageErrorDownloadStatusCodeKey : @(statusCode)}];
}
//......
//进度提交给应用层上层
if (valid) {
for (SDWebImageDownloaderProgressBlock progressBlock in [self callbacksForKey:kProgressCallbackKey]) {
progressBlock(0, expected, self.request.URL);
}
} else {
disposition = NSURLSessionResponseCancel;
}
//发出通知
__block typeof(self) strongSelf = self;
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadReceiveResponseNotification object:strongSelf];
});

if (completionHandler) {
completionHandler(disposition);
}
}

在收到图像数据后会回调didReceiveData代理,在didReceiveData中回将收到的图片数据追加到imageData,这里比较关键的是渐进式图片解码,顾名思义就是拿到多少数据就立刻解码交给上层展示,最后将当前下载进度通知给业务层,对应的关键部分看下面的注解:

- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data {
if (!self.imageData) {
self.imageData = [[NSMutableData alloc] initWithCapacity:self.expectedSize];
}
//将数据追加到self.imageData
[self.imageData appendData:data];
self.receivedSize = self.imageData.length;
if (self.expectedSize == 0) {
// Unknown expectedSize, immediately call progressBlock and return
for (SDWebImageDownloaderProgressBlock progressBlock in [self callbacksForKey:kProgressCallbackKey]) {
progressBlock(self.receivedSize, self.expectedSize, self.request.URL);
}
return;
}
// 获取当前的下载状态,是否结束
BOOL finished = (self.receivedSize >= self.expectedSize);
// 计算当前下载进度
double currentProgress = (double)self.receivedSize / (double)self.expectedSize;
double previousProgress = self.previousProgress;
double progressInterval = currentProgress - previousProgress;
// 只有在大于最小的进度间隔才会通知上层block
if (!finished && (progressInterval < self.minimumProgressInterval)) {
return;
}
self.previousProgress = currentProgress;
//如果使用数据解码器将会禁止渐进式解码,
BOOL supportProgressive = (self.options & SDWebImageDownloaderProgressiveLoad) && !self.decryptor;
if (supportProgressive) {
//获得当前的图片数据
NSData *imageData = [self.imageData copy];
//在解码队列进行渐进式解码
dispatch_async(self.coderQueue, ^{
@autoreleasepool {
UIImage *image = SDImageLoaderDecodeProgressiveImageData(imageData, self.request.URL, finished, self, [[self class] imageOptionsFromDownloaderOptions:self.options], self.context);
if (image) {
//将数据交给业务层,注意这里的finished为NO,因为还没下载完
[self callCompletionBlocksWithImage:image imageData:nil error:nil finished:NO];
}
}
});
}
//从callbacksForKey中取出对应的进度回调block,将进度上传给业务层
for (SDWebImageDownloaderProgressBlock progressBlock in [self callbacksForKey:kProgressCallbackKey]) {
progressBlock(self.receivedSize, self.expectedSize, self.request.URL);
}
}

我们重点看下解码部分:

UIImage * _Nullable SDImageLoaderDecodeProgressiveImageData(NSData * _Nonnull imageData, NSURL * _Nonnull imageURL, BOOL finished,  id<SDWebImageOperation> _Nonnull operation, SDWebImageOptions options, SDWebImageContext * _Nullable context) {

//.......

//获取缓存key
UIImage *image;
id<SDWebImageCacheKeyFilter> cacheKeyFilter = context[SDWebImageContextCacheKeyFilter];
NSString *cacheKey;
if (cacheKeyFilter) {
cacheKey = [cacheKeyFilter cacheKeyForURL:imageURL];
} else {
cacheKey = imageURL.absoluteString;
}

//获取scale
BOOL decodeFirstFrame = SD_OPTIONS_CONTAINS(options, SDWebImageDecodeFirstFrameOnly);
NSNumber *scaleValue = context[SDWebImageContextImageScaleFactor];
CGFloat scale = scaleValue.doubleValue >= 1 ? scaleValue.doubleValue : SDImageScaleFactorForKey(cacheKey);

//构建解码Option
SDImageCoderOptions *coderOptions = @{SDImageCoderDecodeFirstFrameOnly : @(decodeFirstFrame), SDImageCoderDecodeScaleFactor : @(scale)};
if (context) {
SDImageCoderMutableOptions *mutableCoderOptions = [coderOptions mutableCopy];
[mutableCoderOptions setValue:context forKey:SDImageCoderWebImageContext];
coderOptions = [mutableCoderOptions copy];
}

//获取对应的渐进式解码器
id<SDProgressiveImageCoder> progressiveCoder = objc_getAssociatedObject(operation, SDImageLoaderProgressiveCoderKey);
if (!progressiveCoder) {
// We need to create a new instance for progressive decoding to avoid conflicts
for (id<SDImageCoder>coder in [SDImageCodersManager sharedManager].coders.reverseObjectEnumerator) {
if ([coder conformsToProtocol:@protocol(SDProgressiveImageCoder)] &&
[((id<SDProgressiveImageCoder>)coder) canIncrementalDecodeFromData:imageData]) {
//通过Options来创建渐进式解码器
progressiveCoder = [[[coder class] alloc] initIncrementalWithOptions:coderOptions];
break;
}
}
objc_setAssociatedObject(operation, SDImageLoaderProgressiveCoderKey, progressiveCoder, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
//......
//将图片数据更新到渐进式解码器
[progressiveCoder updateIncrementalData:imageData finished:finished];
//..........
if (!image) {
//进行解码
image = [progressiveCoder incrementalDecodedImageWithOptions:coderOptions];
}
if (image) {
//是否需要解码
BOOL shouldDecode = !SD_OPTIONS_CONTAINS(options, SDWebImageAvoidDecodeImage);
if ([image.class conformsToProtocol:@protocol(SDAnimatedImage)]) {
// `SDAnimatedImage` do not decode
shouldDecode = NO;
} else if (image.sd_isAnimated) {
// animated image do not decode
shouldDecode = NO;
}
//如果需要解码则调用decodedImageWithImage进行解码
if (shouldDecode) {
image = [SDImageCoderHelper decodedImageWithImage:image];
}
image.sd_isIncremental = YES;
}

return image;
}

上面最关键的部分在于:

[progressiveCoder updateIncrementalData:imageData finished:finished];
image = [progressiveCoder incrementalDecodedImageWithOptions:coderOptions];
if (image) {
//是否需要解码
BOOL shouldDecode = !SD_OPTIONS_CONTAINS(options, SDWebImageAvoidDecodeImage);
if ([image.class conformsToProtocol:@protocol(SDAnimatedImage)]) {
// `SDAnimatedImage` do not decode
shouldDecode = NO;
} else if (image.sd_isAnimated) {
// animated image do not decode
shouldDecode = NO;
}
//如果需要解码则调用decodedImageWithImage进行解码
if (shouldDecode) {
image = [SDImageCoderHelper decodedImageWithImage:image];
}
// mark the image as progressive (completionBlock one are not mark as progressive)
image.sd_isIncremental = YES;
}

这里渐进式解码器也有好多种,我们仅仅以其中的一种SDImageIOAnimatedCoder为例子:

- (void)updateIncrementalData:(NSData *)data finished:(BOOL)finished {
//.......
//更新数据
_imageData = data;
_finished = finished;
//通过接口更新图片数据
CGImageSourceUpdateData(_imageSource, (__bridge CFDataRef)data, finished);
if (_width + _height == 0) {
//读取图片属性
CFDictionaryRef properties = CGImageSourceCopyPropertiesAtIndex(_imageSource, 0, NULL);
if (properties) {
//获取到图片的宽高
CFTypeRef val = CFDictionaryGetValue(properties, kCGImagePropertyPixelHeight);
if (val) CFNumberGetValue(val, kCFNumberLongType, &_height);
val = CFDictionaryGetValue(properties, kCGImagePropertyPixelWidth);
if (val) CFNumberGetValue(val, kCFNumberLongType, &_width);
CFRelease(properties);
}
}
//检查帧的有效性
[self scanAndCheckFramesValidWithImageSource:_imageSource];
}

- (BOOL)scanAndCheckFramesValidWithImageSource:(CGImageSourceRef)imageSource {
//......
//获取图片数据的帧数
NSUInteger frameCount = CGImageSourceGetCount(imageSource);
NSUInteger loopCount = [self.class imageLoopCountWithSource:imageSource];
NSMutableArray<SDImageIOCoderFrame *> *frames = [NSMutableArray array];

for (size_t i = 0; i < frameCount; i++) {
SDImageIOCoderFrame *frame = [[SDImageIOCoderFrame alloc] init];
//获取帧序列
frame.index = i;
//获取帧时长
frame.duration = [self.class frameDurationAtIndex:i source:imageSource];
//将其添加到frames
[frames addObject:frame];
}

_frameCount = frameCount;
_loopCount = loopCount;
_frames = [frames copy];

return YES;
}
- (UIImage *)incrementalDecodedImageWithOptions:(SDImageCoderOptions *)options {
UIImage *image;
if (_width + _height > 0) {
// 取出当前的第一帧
CGImageRef partialImageRef = CGImageSourceCreateImageAtIndex(_imageSource, 0, NULL);
if (partialImageRef) {
CGFloat scale = _scale;
NSNumber *scaleFactor = options[SDImageCoderDecodeScaleFactor];
if (scaleFactor != nil) {
scale = MAX([scaleFactor doubleValue], 1);
}
image = [[UIImage alloc] initWithCGImage:partialImageRef scale:scale orientation:UIImageOrientationUp];
CGImageRelease(partialImageRef);
image.sd_imageFormat = self.class.imageFormat;
}
}
return image;
}

上面的代码主要就是在数据帧来的时候更新数据源,然后解码数据,取出当前帧的第一帧构建后返回。

紧接着就会根据当前的情况除了动图都会对其进行解码,我们来看下解码部分的代码:

+ (UIImage *)decodedImageWithImage:(UIImage *)image {
if (![self shouldDecodeImage:image]) {
return image;
}

CGImageRef imageRef = [self CGImageCreateDecoded:image.CGImage];
if (!imageRef) {
return image;
}
UIImage *decodedImage = [[UIImage alloc] initWithCGImage:imageRef scale:image.scale orientation:image.imageOrientation];
CGImageRelease(imageRef);
decodedImage.sd_isDecoded = YES;
decodedImage.sd_imageFormat = image.sd_imageFormat;
return decodedImage;
}

嗯,这块不是很难,过一下就可以了。

最后我们看下下载完成后的处理:

- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {

//......
if (error) {
//.........
} else {

if ([self callbacksForKey:kCompletedCallbackKey].count > 0) {
NSData *imageData = [self.imageData copy];
self.imageData = nil;
// 使用数据解密器对图片数据进行解密
if (imageData && self.decryptor) {
imageData = [self.decryptor decryptedDataWithData:imageData response:self.response];
}
if (imageData) {
if (self.options & SDWebImageDownloaderIgnoreCachedResponse && [self.cachedData isEqualToData:imageData]) {
//...........
} else {
// 对图像数据进行解码
dispatch_async(self.coderQueue, ^{
@autoreleasepool {
UIImage *image = SDImageLoaderDecodeImageData(imageData, self.request.URL, [[self class] imageOptionsFromDownloaderOptions:self.options], self.context);
CGSize imageSize = image.size;
if (imageSize.width == 0 || imageSize.height == 0) {
[self callCompletionBlocksWithError:[NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorBadImageData userInfo:@{NSLocalizedDescriptionKey : @"Downloaded image has 0 pixels"}]];
} else {
[self callCompletionBlocksWithImage:image imageData:imageData error:nil finished:YES];
}
[self done];
}
});
}
} else {
//......
}
} else {
[self done];
}
}
}

下载完成后首先会对数据进行解密,然后再对解密后的数据进行解码,最后将数据通过block传递到业务层。
解密部分是通过SDImageLoaderDecodeImageData方法,这部分代码和上面介绍渐进式解码的类似,这里只注重流程,先不对这部分细节进行展开了。

UIImage * _Nullable SDImageLoaderDecodeImageData(NSData * _Nonnull imageData, NSURL * _Nonnull imageURL, SDWebImageOptions options, SDWebImageContext * _Nullable context) {

//......
//调用解码器进行解码
if (!image) {
image = [[SDImageCodersManager sharedManager] decodedImageWithData:imageData options:coderOptions];
}
if (image) {
BOOL shouldDecode = !SD_OPTIONS_CONTAINS(options, SDWebImageAvoidDecodeImage);
if ([image.class conformsToProtocol:@protocol(SDAnimatedImage)]) {
// `SDAnimatedImage` do not decode
shouldDecode = NO;
} else if (image.sd_isAnimated) {
// animated image do not decode
shouldDecode = NO;
}

if (shouldDecode) {
BOOL shouldScaleDown = SD_OPTIONS_CONTAINS(options, SDWebImageScaleDownLargeImages);
if (shouldScaleDown) {
image = [SDImageCoderHelper decodedAndScaledDownImageWithImage:image limitBytes:0];
} else {
image = [SDImageCoderHelper decodedImageWithImage:image];
}
}
}

return image;
}

到目前为止已经带大家过了一遍整个图片下载的流程,在解码结束后通过completeBlock交给上层后还没结束,我们看下completeBlock到底做了啥,让我们回到callDownloadProcessForOperation:

下面是completeBlock代码:

@strongify(operation);
//用户取消请求
if (!operation || operation.isCancelled) {
[self callCompletionBlockForOperation:operation completion:completedBlock error:[NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorCancelled userInfo:nil] url:url];
} else if (cachedImage && options & SDWebImageRefreshCached && [error.domain isEqualToString:SDWebImageErrorDomain] && error.code == SDWebImageErrorCacheNotModified) {
// 强制刷新缓存的时候,在前面先将缓存图片通过completeBlock 将数据传递给上层,到这里将数据加载完毕后,不做任何操作
} else if ([error.domain isEqualToString:SDWebImageErrorDomain] && error.code == SDWebImageErrorCancelled) {
// 将错误交给上层
[self callCompletionBlockForOperation:operation completion:completedBlock error:error url:url];
} else if (error) {
[self callCompletionBlockForOperation:operation completion:completedBlock error:error url:url];
//是否需要将错误的URL添加到failedURLs
BOOL shouldBlockFailedURL = [self shouldBlockFailedURLWithURL:url error:error];
if (shouldBlockFailedURL) {
SD_LOCK(self.failedURLsLock);
[self.failedURLs addObject:url];
SD_UNLOCK(self.failedURLsLock);
}
} else {
// 成功
//如果SDWebImageRetryFailed = 1 表示错误的url需要重试,这时候就要将当前的url从failedURLs移除
if ((options & SDWebImageRetryFailed)) {
SD_LOCK(self.failedURLsLock);
[self.failedURLs removeObject:url];
SD_UNLOCK(self.failedURLsLock);
}
//成功后会对当前数据进行缓存
[self callStoreCacheProcessForOperation:operation url:url options:options context:context downloadedImage:downloadedImage downloadedData:downloadedData finished:finished progress:progressBlock completed:completedBlock];
}
if (finished) {
//加载成功后将操作从Running队列中移除
[self safelyRemoveOperationFromRunning:operation];
}

completeBlock里面有很多的情况会调到,比如NSOperation被取消,发生错误,成功等,下面我们只强调如下几种情况:

  • 下载失败的处理方式
[self callCompletionBlockForOperation:operation completion:completedBlock error:error url:url];
//是否需要将错误的URL添加到failedURLs
BOOL shouldBlockFailedURL = [self shouldBlockFailedURLWithURL:url error:error];
if (shouldBlockFailedURL) {
SD_LOCK(self.failedURLsLock);
[self.failedURLs addObject:url];
SD_UNLOCK(self.failedURLsLock);
}

如果下载失败那么会将错误通过callCompletionBlockForOperation将错误返回给业务层,并且会将错误的url添加到failedURLs,如果我们配置不重试错误的url,那么在failedURLs数组中的url将不会发起请求。

  • 下载成功的处理方式
//如果SDWebImageRetryFailed = 1 表示错误的url需要重试,这时候就要将当前的url从failedURLs移除
if ((options & SDWebImageRetryFailed)) {
SD_LOCK(self.failedURLsLock);
[self.failedURLs removeObject:url];
SD_UNLOCK(self.failedURLsLock);
}
//成功后会对当前数据进行缓存
[self callStoreCacheProcessForOperation:operation url:url options:options context:context downloadedImage:downloadedImage downloadedData:downloadedData finished:finished progress:progressBlock completed:completedBlock];

和上面相反,如果下载成功后会将之前添加到failedURLs的url移除,然后调用callStoreCacheProcessForOperation将数据添加到缓存中。

- (void)callStoreCacheProcessForOperation:(nonnull SDWebImageCombinedOperation *)operation
url:(nonnull NSURL *)url
options:(SDWebImageOptions)options
context:(SDWebImageContext *)context
downloadedImage:(nullable UIImage *)downloadedImage
downloadedData:(nullable NSData *)downloadedData
finished:(BOOL)finished
progress:(nullable SDImageLoaderProgressBlock)progressBlock
completed:(nullable SDInternalCompletionBlock)completedBlock {

// 获取缓存类型
SDImageCacheType storeCacheType = SDImageCacheTypeAll;
if (context[SDWebImageContextStoreCacheType]) {
storeCacheType = [context[SDWebImageContextStoreCacheType] integerValue];
}

SDImageCacheType originalStoreCacheType = SDImageCacheTypeNone;
if (context[SDWebImageContextOriginalStoreCacheType]) {
originalStoreCacheType = [context[SDWebImageContextOriginalStoreCacheType] integerValue];
}

//生成缓存key
id<SDWebImageCacheKeyFilter> cacheKeyFilter = context[SDWebImageContextCacheKeyFilter];
NSString *key = [self cacheKeyForURL:url cacheKeyFilter:cacheKeyFilter];
//图片转换器
id<SDImageTransformer> transformer = context[SDWebImageContextImageTransformer];
//缓存图片处理器
id<SDWebImageCacheSerializer> cacheSerializer = context[SDWebImageContextCacheSerializer];

//是否需要对图片进行处理
BOOL shouldTransformImage = downloadedImage && (!downloadedImage.sd_isAnimated || (options & SDWebImageTransformAnimatedImage)) && transformer;
//是否缓存原始图片
BOOL shouldCacheOriginal = downloadedImage && finished;

//原图缓存
//需要缓存原始图片的情况下
if (shouldCacheOriginal) {
//正常情况下我们使用cache type,但是如果目标图片是转换过的,我们会使用original 存储缓存类型来缓存
SDImageCacheType targetStoreCacheType = shouldTransformImage ? originalStoreCacheType : storeCacheType;
if (cacheSerializer && (targetStoreCacheType == SDImageCacheTypeDisk || targetStoreCacheType == SDImageCacheTypeAll)) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
@autoreleasepool {
//将数据通过cacheSerializer处理后返回
NSData *cacheData = [cacheSerializer cacheDataWithImage:downloadedImage originalData:downloadedData imageURL:url];
//将数据添加到缓存中,具体缓存到哪里根据targetStoreCacheType类型
[self.imageCache storeImage:downloadedImage imageData:cacheData forKey:key cacheType:targetStoreCacheType completion:nil];
}
});
} else {
[self.imageCache storeImage:downloadedImage imageData:downloadedData forKey:key cacheType:targetStoreCacheType completion:nil];
}
}
// 转换后图片缓存
if (shouldTransformImage) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
@autoreleasepool {
//对数据进行转换
UIImage *transformedImage = [transformer transformedImageWithImage:downloadedImage forKey:key];
if (transformedImage && finished) {
NSString *transformerKey = [transformer transformerKey];
//缓存的图片带转换信息
NSString *cacheKey = SDTransformedKeyForKey(key, transformerKey);
//判断图片是否被转换成功
BOOL imageWasTransformed = ![transformedImage isEqual:downloadedImage];
NSData *cacheData;
// pass nil if the image was transformed, so we can recalculate the data from the image
if (cacheSerializer && (storeCacheType == SDImageCacheTypeDisk || storeCacheType == SDImageCacheTypeAll)) {
//将数据通过cacheSerializer处理后返回
cacheData = [cacheSerializer cacheDataWithImage:transformedImage originalData:(imageWasTransformed ? nil : downloadedData) imageURL:url];
} else {
cacheData = (imageWasTransformed ? nil : downloadedData);
}
//缓存图片数据
[self.imageCache storeImage:transformedImage imageData:cacheData forKey:cacheKey cacheType:storeCacheType completion:nil];
}
//将结果传递到业务层
[self callCompletionBlockForOperation:operation completion:completedBlock image:transformedImage data:downloadedData error:nil cacheType:SDImageCacheTypeNone finished:finished url:url];
}
});
} else {
[self callCompletionBlockForOperation:operation completion:completedBlock image:downloadedImage data:downloadedData error:nil cacheType:SDImageCacheTypeNone finished:finished url:url];
}
}
2.3 设置图片

设置图片环节比较简单就是根据view的类型来设置对应的finalSetImageBlock,然后看是否有过场动画,如果有则播放过场动画后执行finalSetImageBlock。

- (void)sd_setImage:(UIImage *)image imageData:(NSData *)imageData basedOnClassOrViaCustomSetImageBlock:(SDSetImageBlock)setImageBlock transition:(SDWebImageTransition *)transition cacheType:(SDImageCacheType)cacheType imageURL:(NSURL *)imageURL {
// 根据view的类型来设置对应的finalSetImageBlock
UIView *view = self;
SDSetImageBlock finalSetImageBlock;
if (setImageBlock) {
finalSetImageBlock = setImageBlock;
} else if ([view isKindOfClass:[UIImageView class]]) {
UIImageView *imageView = (UIImageView *)view;
finalSetImageBlock = ^(UIImage *setImage, NSData *setImageData, SDImageCacheType setCacheType, NSURL *setImageURL) {
imageView.image = setImage;
};
}
else if ([view isKindOfClass:[UIButton class]]) {
UIButton *button = (UIButton *)view;
finalSetImageBlock = ^(UIImage *setImage, NSData *setImageData, SDImageCacheType setCacheType, NSURL *setImageURL) {
[button setImage:setImage forState:UIControlStateNormal];
};
}
//播放过场动画
if (transition) {
[UIView transitionWithView:view duration:0 options:0 animations:^{
// 0 duration to let UIKit render placeholder and prepares block
if (transition.prepares) {
transition.prepares(view, image, imageData, cacheType, imageURL);
}
} completion:^(BOOL finished) {
[UIView transitionWithView:view duration:transition.duration options:transition.animationOptions animations:^{
if (finalSetImageBlock && !transition.avoidAutoSetImage) {
//执行finalSetImageBlock
finalSetImageBlock(image, imageData, cacheType, imageURL);
}
if (transition.animations) {
transition.animations(view, image);
}
} completion:transition.completion];
}];
} else {
//如果没有过场动画直接执行finalSetImageBlock
if (finalSetImageBlock) {
finalSetImageBlock(image, imageData, cacheType, imageURL);
}
}
}

简要流程如下图所示:

2.4 整体架构总结

上面几个章节我们将整个流程过了一遍,接下来我们将会从宏观的角度来浏览下整个架构,整个SDWebImage代码量还是蛮庞大的,所以如果没有缕清整个结构的话很容易陷入代码之中,SDWebImage之所以大主要是因为它整个架构可定制的部件多,并且每个部件都可以有不同的选择,这种框架最适合采用Core+Plugin形式,SDWebImage 有个不足的地方就是代码的目录管理做得不好,整个庞大的项目都堆到一个目录下,让刚接触SDWebImage会显得毫无头绪,下面是重新组织的目录结构:

下面是SDWebImage官方的架构图,以及关键的类图:



下面我们结合上面三张图来过一遍整个项目:

1. 常用控件的分类
SDWebImage为UIImageView,NSButton,SDAnimatedImageView这些类都创建了对应都分类,方便这些类加载网络图片资源,大家可以通过搜索+WebCache或者+HightlightedWebCache可以找到这些分类。这些分类中除了提供设置网络图片的方法外,还提供了设置下载指示器,以及图片转换处理器等方法。UIView+WebCache.h是这些分类的基础。

2. 图片预取器

SDWebImagePrefetcher 用于图片的预取:

- (nullable SDWebImagePrefetchToken *)prefetchURLs:(nullable NSArray<NSURL *> *)urls;

这个是专门供外部使用的,SDWebImageView内部并没有使用到这个类.图片预取顾名思义就是在没使用图片之前就先将图片给下载到缓存中,这样使用的时候加载速度就会块很多,这一般是针对比较常用的图片资源。SDWebImagePrefetcherDelegate 会在全部下载结束后,或者单个资源下载完成后进行回调。

3. 图片管理器

SDWebImageManager是整个SDWebImage的核心它持有:

* imageLoader(遵循SDImageLoader)用于从网络上加载图片
* mageCache(遵循SDImageCache)用于从缓存中获取图片
* cacheKeyFilter(遵循SDWebImageCacheKeyFilter)用于缓存key的生成
* cacheSerializer(遵循SDWebImageCacheSerializer)用于对解码后的图片数据进行序列化
* transformer(遵循SDImageTransformer)用于图片后期处理
* optionsProcessor(遵循SDWebImageOptionsProcessor)用于提供全局的option设置。

它通过:

- (nullable SDWebImageCombinedOperation *)loadImageWithURL:(nullable NSURL *)url
options:(SDWebImageOptions)options
context:(nullable SDWebImageContext *)context
progress:(nullable SDImageLoaderProgressBlock)progressBlock
completed:(nonnull SDInternalCompletionBlock)completedBlock;

加载图片,这里面包含了全部的加载策略,是整个库的核心

还提供了取消下载的接口:

- (void)cancelAll;

以及查询key的接口:

- (nullable NSString *)cacheKeyForURL:(nullable NSURL *)url;

代理SDWebImageManagerDelegate

用于判断某个URL是否需要下载
- (BOOL)imageManager:(nonnull SDWebImageManager *)imageManager shouldDownloadImageForURL:(nonnull NSURL *)imageURL;
用于判断某个URL发生某个错误是否将其标记为失败的URL
- (BOOL)imageManager:(nonnull SDWebImageManager *)imageManager shouldBlockFailedURL:(nonnull NSURL *)imageURL withError:(nonnull NSError *)error;

3.1 图片转换器

图片转换器是存放在context中的,key为SDWebImageContextImageTransformer,与图片转换器有关的文件如下:

UIImage+Transform.h/.m
SDImageTransformer.h/.m

UIImage+Transform主要是图片转换的实际处理分类,而SDImageTransformer则是负责上层调度的,它会调度UIImage+Transform中的方法对图片进行处理。

我们看下SDWebImage都提供了哪些图片转换器:

SDImagePipelineTransformer 串行流水线图片处理器
之前一直有疑问,context中只能存放一个图片处理器,如果我们需要连续应用多个处理呢?这个对图片库来说是很常见的一个功能,SDImagePipelineTransformer就是用于这种用途,我们只要将所有的处理器添加到transformers数组中就会被应用到当前图片上。
SDImageRoundCornerTransformer 图片圆角处理器
可以指定圆角半径,边界线宽,边界线颜色,圆角位置
SDImageResizingTransformer 缩放处理器
SDImageCroppingTransformer 裁剪处理器
SDImageFlippingTransformer 翻转处理器
SDImageRotationTransformer 旋转处理器
SDImageTintTransformer 颜色修改处理器
SDImageBlurTransformer 模糊处理器
SDImageFilterTransformer 滤镜处理器

3.2 图片加载器

涉及到的类主要有:

SDImageLoader.h/.m
SDImageLoadersManager.h/.m

SDImageLoader里面主要定义了两个解码相关的静态方法SDImageLoaderDecodeImageDataSDImageLoaderDecodeProgressiveImageData,以及SDImageLoader协议。而SDImageLoadersManager则负责管理全部的loader,每个SDWebImageManager都只有一个SDImageLoadersManager,但是SDImageLoadersManager里面却管理着一系列的SDImageLoader,所以整个SDWebImage相当于由多个loader构成。
我们这里看下SDImageLoader

//判断当前url是否需要下载
- (BOOL)canRequestImageForURL:(nullable NSURL *)url;

//请求图片下载
- (nullable id<SDWebImageOperation>)requestImageWithURL:(nullable NSURL *)url
options:(SDWebImageOptions)options
context:(nullable SDWebImageContext *)context
progress:(nullable SDImageLoaderProgressBlock)progressBlock
completed:(nullable SDImageLoaderCompletedBlock)completedBlock;

//判断某个URL发生某个错误的时候是否需要将它判定为失败的url
- (BOOL)shouldBlockFailedURLWithURL:(nonnull NSURL *)url
error:(nonnull NSError *)error;

3.2.1 图片下载器

我们知道iOS中图片的下载使用的还是URLSession,而这里的图片下载器就是通过URLSession进行下载的,这个在上面介绍流程源码的时候已经介绍过了,下面将从宏观的角度对这些类进行分析,整个下载器的组成,这里不追究细节。

SDWebImageDownloaderOptions
下载全局选项
SDWebImageDownloadToken
用于指示某个下载的Token,包括url,request,response,以及取消的方法,在启动下载的时候会返回一个DownloadToken,用它可以进行取消任务。
SDWebImageDownloaderConfig
下载器配置类,包括:

  • 最大并发数,默认为6
  • 下载超时时间,默认为15秒
  • 最小进度间隔,指的是上一次进度和本次进度的差值不能小于这个数,如果小于这个数就不通知进度更新
  • operationClass 下载指定的NSOperation具体实现类
  • executionOrder operations执行顺序,默认是先入先出
  • 认证需要的username,password
  • sessionConfiguration

SDWebImageDownloaderRequestModifier
全局请求拦截修改器,如果返回nil表示某个请求将会被取消,如果requestModifier = nil 表示不修改原始的下载请求。
SDWebImageDownloaderResponseModifier
全局返回体拦截修改器,如果返回nil表示当前下载被取消,如果responseModifier = nil 表示不修改原始返回体
SDWebImageDownloaderDecryptor
解密器,它会在图片解码之前对某些加密的图片进行解密,比如某些图片会进行Base64加密,这时候就需要使用解密器进行解密,如果返回nil表示下载失败
SDWebImageDownloaderOperation

除了上面的几个重要类外,还具备了下载器状态查询接口/属性:suspended是否暂停下载,当前下载数currentDownloadCount。

以及下载,取消下载接口:

- (nullable SDWebImageDownloadToken *)downloadImageWithURL:(nullable NSURL *)url
options:(SDWebImageDownloaderOptions)options
context:(nullable SDWebImageContext *)context
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock;

- (void)cancelAllDownloads;

SDWebImageDownloaderOperation
SDWebImageDownloaderOperation是这里的重头戏,它的输入是NSURLRequest,NSURLSession,SDWebImageDownloaderOptions,SDWebImageContext。
NSURLRequest,NSURLSession不用多说它是用于创建NSURLSessionTask,SDWebImageDownloaderOptions是用于下载的配置选项,SDWebImageContext用于存放重要组件。

下面是一些比较重要的属性:

//请求体
@property (strong, nonatomic, readonly, nullable) NSURLRequest *request;
//返回体
@property (strong, nonatomic, readonly, nullable) NSURLResponse *response;
//返回异常
@property (strong, nonatomic, nullable) NSError *responseError;
//执行下载请求的dataTask
@property (strong, nonatomic, readonly, nullable) NSURLSessionTask *dataTask;
//urlsession
@property (weak, nonatomic, nullable) NSURLSession *unownedSession;
//包括进度更新block以及completeblock
@property (strong, nonatomic, nonnull) NSMutableArray<SDCallbacksDictionary *> *callbackBlocks;
//下载选项
@property (assign, nonatomic, readwrite) SDWebImageDownloaderOptions options;
//重要部件上下文
@property (copy, nonatomic, readwrite, nullable) SDWebImageContext *context;
//图片数据
@property (strong, nonatomic, nullable) NSMutableData *imageData;
//收发数据大小
@property (assign, nonatomic) NSUInteger expectedSize;
@property (assign, nonatomic) NSUInteger receivedSize;
@property (assign, nonatomic) double previousProgress;
//code队列
@property (strong, nonatomic, nonnull) dispatch_queue_t coderQueue;
//当前NSOperation状态
@property (assign, nonatomic, getter = isExecuting) BOOL executing;
@property (assign, nonatomic, getter = isFinished) BOOL finished;
//返回体修改器
@property (strong, nonatomic, nullable) id<SDWebImageDownloaderResponseModifier> responseModifier;
//解密器
@property (strong, nonatomic, nullable) id<SDWebImageDownloaderDecryptor> decryptor;

3.3 图片下载缓存

SDImageCache
SDImageCache是管理图片下载缓存的类,最初的时候个人以为是SDImageCachesManager管理全部的缓存,但是实际上是这货,并且SDImageCachesManager从源码上看并没有实际的用途。我们先来看下SDImageCache,它的重要元素包含:
缓存配置:SDImageCacheConfig
内存缓存:memoryCache
磁盘缓存:diskCache,磁盘缓存根路径 diskCachePath

SDMemoryCache

基于NSCache的内存缓存,主要对象包括缓存配置SDImageCacheConfig,以及缓存对象的增删改查。

SDDiskCache
基于文件的内存缓存,主要对象包括缓存配置SDImageCacheConfig,以及缓存对象的增删改查。

SDWebImageCacheKeyFilter

全局根据url映射到缓存key的类。

SDWebImageCacheSerializer

在缓存之前对图像数据进行处理

SDImageCacheConfig
SDImageCacheOptions

缓存配置

SDImageCacheDefine

缓存模块相关的定义

SDImageCachesManager

用于管理缓存的管理器,但是目前在项目中没有使用

SDImageCachesManagerOperation
用在SDImageCachesManager也是一个NSOperation

同时需要强调的是SDWebImage支持以插件的形式加入YYImage以及YYCache.具体见:SDWebImageYYPlugin
后面也会对这个开源库进行分析。

3.4 图片编解码器

SDImageCodersManager

SDImageCodersManager 顾名思义就是用于全局得管理编解码器,后面添加的编解码器有最高的优先级

SDImageCoderOption
解码器配置选项

SDImageCoder/SDProgressiveImageCoder

SDImageCoder 是一个比较重要的协议,是所有编解码器都需要遵循的协议:

@protocol SDImageCoder <NSObject>

@required
//给定的数据能否被当前编解码器解码
- (BOOL)canDecodeFromData:(nullable NSData *)data;

//使用当前解码器对图像数据进行解码
- (nullable UIImage *)decodedImageWithData:(nullable NSData *)data
options:(nullable SDImageCoderOptions *)options;

#pragma mark - Encoding

//当前编解码器是否能够编码到指定的格式
- (BOOL)canEncodeToFormat:(SDImageFormat)format NS_SWIFT_NAME(canEncode(to:));

//使用当前编码器对图像数据进行编码
- (nullable NSData *)encodedDataWithImage:(nullable UIImage *)image
format:(SDImageFormat)format
options:(nullable SDImageCoderOptions *)options;
@end

@protocol SDProgressiveImageCoder <SDImageCoder>

@required

//是否能够渐进解码
- (BOOL)canIncrementalDecodeFromData:(nullable NSData *)data;

- (nonnull instancetype)initIncrementalWithOptions:(nullable SDImageCoderOptions *)options;

//更新增量图片数据
- (void)updateIncrementalData:(nullable NSData *)data finished:(BOOL)finished;

//进行渐进式解码
- (nullable UIImage *)incrementalDecodedImageWithOptions:(nullable SDImageCoderOptions *)options;

@end

SDAnimatedImageProvider
用于提供基本动图所需要遵循的协议,目前SDAnimatedImage以及SDAnimatedImageCoder会遵循这个协议。

SDImageIOCoder

用于对PNG, JPEG, TIFF 这些格式进行编解码,它也支持GIF以及HEIC这些动图格式,但是GIF只支持第一帧,HEIC需要看各个系统的支持情况。对于这些动图建议使用SDAnimatedImageView

SDAnimatedImageCoder
SDAnimatedImageCoder是所有动图编解码器的基类,下面将要介绍的SDImageAPNGCoder,SDImageGIFCoder,SDImageHEICCoder都是这个类的子类。

SDImageAPNGCoder
APNG动画格式编解码器,关于APNG格式介绍,大家可以查看如下文章:

SDImageCoderHelper
SDImageCoderHelper是上层编解码器的公共方法类。

3.5 动图相关类

SDAnimatedImage
SDAnimatedImage 为动图数据类和UIImage对应
SDAnimatedImageView
SDAnimatedImageView 为动图显示View和UIImageView类对应,内部持有SDAnimatedImagePlayer
SDAnimatedImagePlayer
SDAnimatedImagePlayer 用于播放动画
SDWebImageTransition
SDWebImageTransition为图片加载结束后呈现图片时候使用的转场动画,目前支持fadeTransition,flipFromLeftTransition,flipFromRightTransition,flipFromTopTransition,flipFromBottomTransition,curlUpTransition,curlDownTransition几种。

****3.6 SDWebImageOptionsProcessor ****

全局Options处理器,用于针对所有的Options设置进行过滤处理。

****3.7 SDWebImageIndicator ****

进度指示器,目前主要支持两种:SDWebImageActivityIndicator和SDWebImageProgressIndicator

Contents
  1. 1. 1. 开源代码信息
  2. 2. 2. 源码解析
    1. 2.1. 2.1 总入口
    2. 2.2. 2.2 图片加载
    3. 2.3. 2.2.1 查询缓存还是从网络下载
    4. 2.4. 2.2.1.1 使用缓存资源
    5. 2.5. 2.2.1.2 使用网络资源
    6. 2.6. 2.3 设置图片
    7. 2.7. 2.4 整体架构总结