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 ]; NSString *validOperationKey = context[SDWebImageContextSetImageOperationKey]; if (!validOperationKey) { validOperationKey = NSStringFromClass ([self class ]); } self .sd_latestOperationKey = validOperationKey; [self sd_cancelImageLoadOperationWithKey:validOperationKey]; 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 ); 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 (imageProgress && finished && !error && imageProgress.totalUnitCount == 0 && imageProgress.completedUnitCount == 0 ) { imageProgress.totalUnitCount = SDWebImageProgressUnitCountUnknown; imageProgress.completedUnitCount = SDWebImageProgressUnitCountUnknown; } if (finished) { [self sd_stopImageIndicator]; } BOOL shouldCallCompletedBlock = finished || (options & SDWebImageAvoidAutoSetImage); BOOL shouldNotSetImage = ((image && (options & SDWebImageAvoidAutoSetImage)) || (!image && !(options & SDWebImageDelayPlaceholder))); SDWebImageNoParamsBlock callCompletedBlockClojure = ^{ if (!self ) { return ; } if (!shouldNotSetImage) { [self sd_setNeedsLayout]; } if (completedBlock && shouldCallCompletedBlock) { completedBlock(image, data, error, cacheType, finished, url); } }; if (shouldNotSetImage) { dispatch_main_async_safe(callCompletedBlockClojure); return ; } UIImage *targetImage = nil ; NSData *targetData = nil ; if (image) { targetImage = image; targetData = data; } else if (options & SDWebImageDelayPlaceholder) { targetImage = placeholder; targetData = nil ; } 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(); }); }]; [self sd_setImageLoadOperation:operation forKey:validOperationKey]; } else { [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 { NSAssert (completedBlock != nil , @"If you mean to prefetch the image, use -[SDWebImagePrefetcher prefetchURLs] instead" ); if ([url isKindOfClass:NSString .class]) { url = [NSURL URLWithString:(NSString *)url]; } if (![url isKindOfClass:NSURL .class]) { url = nil ; } SDWebImageCombinedOperation *operation = [SDWebImageCombinedOperation new]; operation.manager = self ; BOOL isFailedUrl = NO ; if (url) { SD_LOCK(self .failedURLsLock); isFailedUrl = [self .failedURLs containsObject:url]; SD_UNLOCK(self .failedURLsLock); } 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; } SD_LOCK(self .runningOperationsLock); [self .runningOperations addObject:operation]; SD_UNLOCK(self .runningOperationsLock); 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]; } if (!context[SDWebImageContextCacheKeyFilter]) { id<SDWebImageCacheKeyFilter> cacheKeyFilter = self.cacheKeyFilter; [mutableContext setValue:cacheKeyFilter forKey:SDWebImageContextCacheKeyFilter]; } 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 ]; } if (self.optionsProcessor) { result = [self.optionsProcessor processedResultForURL:url options :options context:context]; } if (!result) { result = [[SDWebImageOptionsResult alloc] initWithOptions:options context:context]; } return result; }
它实际上是在context中加入了SDImageTransformer ,SDWebImageContextCacheKeyFilter ,以及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) { id <SDWebImageCacheKeyFilter> cacheKeyFilter = context[SDWebImageContextCacheKeyFilter]; NSString *key = [self cacheKeyForURL:url cacheKeyFilter:cacheKeyFilter]; @weakify(operation); 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) { [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 { if (!key) { if (doneBlock) { doneBlock(nil , nil , SDImageCacheTypeNone); } return nil ; } id <SDImageTransformer> transformer = context[SDWebImageContextImageTransformer]; if (transformer) { NSString *transformerKey = [transformer transformerKey]; key = SDTransformedKeyForKey(key, transformerKey); } UIImage *image = [self imageFromMemoryCacheForKey:key]; if (image) { if (options & SDImageCacheDecodeFirstFrameOnly) { 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) { 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]; 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); } return operation; }
在queryCacheOperationForKey 中做的工作和其他图片加载库类似,只不过在细节会有点差异:
按照key的规则构建出缓存key
优先从缓存中查找缓存,如果只使用内存缓存的则在这个步骤不论是否能够在内存中找到缓存,都通过block返回结果。
如果在内存缓存中没有找到对应的图片缓存,则继续查找磁盘缓存,如果在磁盘中找到了,再看是否需要同步到内存缓存。
最后交付数据到应用层
下面将针对上面几点展开介绍:
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; } NSURL *keyURL = [NSURL URLWithString:key]; NSString *ext = keyURL ? keyURL.pathExtension : key.pathExtension; if (ext.length > 0 ) { if (keyURL && !keyURL.isFileURL) { NSURLComponents *component = [NSURLComponents componentsWithURL:keyURL resolvingAgainstBaseURL:NO]; component.path = [[[component.path.string ByDeletingPathExtension string ByAppendingString:SDImageTransformerKeySeparator] string ByAppendingString:transformerKey] string ByAppendingPathExtension:ext]; return component.URL.absoluteString; } else { return [[[key.string ByDeletingPathExtension string ByAppendingString:SDImageTransformerKeySeparator] string ByAppendingString:transformerKey] string ByAppendingPathExtension:ext]; } } else { return [[key string ByAppendingString:SDImageTransformerKeySeparator] string ByAppendingString: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) { done Block(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) { done Block(diskImage , diskData , cacheType ) ; } else { dispatch_async(dispatch_get_main_queue () , ^{ done Block(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 [[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 = [NSOperationQueue new]; _downloadQueue.maxConcurrentOperationCount = _config.maxConcurrentDownloads; _downloadQueue.name = @"com.hackemist.SDWebImageDownloader" ; _URLOperations = [NSMutableDictionary new]; NSMutableDictionary <NSString *, NSString *> *headerDictionary = [NSMutableDictionary dictionary]; NSString *userAgent = nil ; 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]]; 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; } 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]; } _session = [NSURLSession sessionWithConfiguration:sessionConfiguration delegate:self delegateQueue:nil ]; } return self ; }
在初始化方法中主要负责重要组件的创建****_downloadQueue, _URLOperations, _session****。
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) { if (cachedImage && options & SDWebImageRefreshCached ) { [self callCompletionBlockForOperation:operation completion:completedBlock image:cachedImage data:cachedData error:nil cacheType:cacheType finished:YES url:url]; 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 ) { } 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]; BOOL shouldBlockFailedURL = [self shouldBlockFailedURLWithURL:url error:error]; if (shouldBlockFailedURL) { SD_LOCK (self .failedURLsLock); [self .failedURLs addObject:url]; SD_UNLOCK (self .failedURLsLock); } } else { 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) { [self safelyRemoveOperationFromRunning:operation]; } }]; } else if (cachedImage) { [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]; } }
是否需要从网络上下载图片需要根据如下条件进行判断:
SDWebImageFromCacheOnly = 0,表示不单单只允许使用缓存的数据
上一步没有找到缓存cachedImage
SDWebImageRefreshCached = 1 强制刷新缓存
imageManager:shouldDownloadImageForURL:决定当图片在缓存内没有找到的时候是否需要下载
[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) { downloaderOptions &= ~SDWebImageDownloaderProgressiveLoad; 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; NSOperation <SDWebImageDownloaderOperation > * operation = [self .URLOperations objectForKey:url]; if (! operation || operation.isFinished || operation.isCancelled) { operation = [self createDownloaderOperationWithUrl:url options:options context:context]; @weakify (self ); operation.completionBlock = ^ { @strongify (self ); SD_LOCK (self .operationsLock); [self .URLOperations removeObjectForKey:url]; SD_UNLOCK (self .operationsLock); }; self .URLOperations [url] = operation; [self .downloadQueue addOperation:operation]; downloadOperationCancelToken = [operation addHandlersForProgress:progressBlock completed:completedBlock]; } else { @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 * 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,将当前的progressBlock ,completedBlock 添加到 已经存在的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 ; } 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); SDWebImageMutableContext *mutableContext; if (context) { mutableContext = [context mutableCopy]; } else { mutableContext = [NSMutableDictionary dictionary]; } 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 (!modifiedRequest) { return nil ; } else { request = [modifiedRequest copy ]; } } else { request = [mutableRequest copy ]; } 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 ]; Class operationClass = self .config.operationClass; if (operationClass && [operationClass isSubclassOfClass:[NSOperation class ]] && [operationClass conformsToProtocol:@protocol (SDWebImageDownloaderOperation )]) { } else { operationClass = [SDWebImageDownloaderOperation class ]; } 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) { 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 ]; _options = options; _context = [context copy ]; _callbackBlocks = [NSMutableArray new]; _responseModifier = context[SDWebImageContextDownloadResponseModifier]; _decryptor = context[SDWebImageContextDownloadDecryptor]; _executing = NO ; _finished = NO ; _expectedSize = 0 ; _unownedSession = session; _coderQueue = dispatch_queue_create("com.hackemist.SDWebImageDownloaderOperationCoderQueue" , DISPATCH_QUEUE_SERIAL); _backgroundTaskId = UIBackgroundTaskInvalid ; } return self ; }
接下来我们带着大家来解决如下几个问题:
SDWebImageDownloaderOperation是如何使用NSSession发起下载请求的?
请求后通过代理返回的数据怎么传到SDWebImageDownloaderOperation中?在SDWebImageDownloaderOperation又是怎么做处理的。
图片下载后是怎么通知给上层的,下载进度又是如何传递的?
我们先来看下第一个问题:
SDWebImageDownloaderOperation是如何使用NSSession发起下载请求的?
这个比较好回答,我们知道NSOperation运行会调用它的start方法,所以我们要回答这个问题也应该从这个方法入手:
- (void)start { @synchronized (self ) { if (self .isCancelled) { self .finished = YES ; [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]; }]; } NSURLSession * session = self .unownedSession; 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 { 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 ; 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 ; 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 appendData:data]; self .receivedSize = self .imageData.length; if (self .expectedSize == 0 ) { 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; 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) { [self callCompletionBlocksWithImage:image imageData:nil error:nil finished:NO ]; } } }); } 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 ) { UIImage *image; id<SDWebImageCacheKeyFilter> cacheKeyFilter = context[SDWebImageContextCacheKeyFilter ] ; NSString *cacheKey; if (cacheKeyFilter) { cacheKey = [cacheKeyFilter cacheKeyForURL :imageURL ] ; } else { cacheKey = imageURL.absoluteString; } BOOL decodeFirstFrame = SD_OPTIONS_CONTAINS(options , SDWebImageDecodeFirstFrameOnly) ; NSNumber *scaleValue = context[SDWebImageContextImageScaleFactor ] ; CGFloat scale = scaleValue.doubleValue >= 1 ? scaleValue.doubleValue : SDImageScaleFactorForKey(cacheKey ) ; 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) { for (id<SDImageCoder>coder in [SDImageCodersManager sharedManager ] .coders.reverseObjectEnumerator) { if ([coder conformsToProtocol :@protocol (SDProgressiveImageCoder )] && [((id <SDProgressiveImageCoder >)coder ) canIncrementalDecodeFromData :imageData ] ) { 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 )] ) { shouldDecode = NO; } else if (image.sd_isAnimated) { shouldDecode = NO; } 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)]) { shouldDecode = NO; } else if (image.sd_isAnimated) { shouldDecode = NO; } if (shouldDecode) { image = [SDImageCoderHelper decodedImageWithImage:image]; } 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 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 )] ) { shouldDecode = NO; } else if (image.sd_isAnimated) { 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 ) { } 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]; BOOL shouldBlockFailedURL = [self shouldBlockFailedURLWithURL:url error:error]; if (shouldBlockFailedURL) { SD_LOCK (self .failedURLsLock); [self .failedURLs addObject:url]; SD_UNLOCK (self .failedURLsLock); } } else { 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) { [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将不会发起请求。
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]; } 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) { SDImageCacheType targetStoreCacheType = shouldTransformImage ? originalStoreCacheType : storeCacheType; if (cacheSerializer && (targetStoreCacheType == SDImageCacheTypeDisk || targetStoreCacheType == SDImageCacheTypeAll)) { dispatch_async (dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0 ), ^{ @autoreleasepool { NSData *cacheData = [cacheSerializer cacheDataWithImage:downloadedImage originalData:downloadedData imageURL:url]; [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; if (cacheSerializer && (storeCacheType == SDImageCacheTypeDisk || storeCacheType == SDImageCacheTypeAll)) { 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 { 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:^{ 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(image, imageData, cacheType, imageURL); } if (transition.animations) { transition.animations(view, image); } } completion:transition.completion]; }]; } else { 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;
加载图片,这里面包含了全部的加载策略,是整个库的核心
还提供了取消下载的接口:
以及查询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里面主要定义了两个解码相关的静态方法SDImageLoaderDecodeImageData ,SDImageLoaderDecodeProgressiveImageData ,以及SDImageLoader协议。而SDImageLoadersManager则负责管理全部的loader,每个SDWebImageManager都只有一个SDImageLoadersManager,但是SDImageLoadersManager里面却管理着一系列的SDImageLoader,所以整个SDWebImage相当于由多个loader构成。 我们这里看下SDImageLoader
- (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; - (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;@property (strong , nonatomic , readonly , nullable ) NSURLSessionTask *dataTask;@property (weak , nonatomic , nullable ) NSURLSession *unownedSession;@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;@property (strong , nonatomic , nonnull ) dispatch_queue_t coderQueue;@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类对应,内部持有SDAnimatedImagePlayerSDAnimatedImagePlayer SDAnimatedImagePlayer 用于播放动画SDWebImageTransition SDWebImageTransition为图片加载结束后呈现图片时候使用的转场动画,目前支持fadeTransition,flipFromLeftTransition,flipFromRightTransition,flipFromTopTransition,flipFromBottomTransition,curlUpTransition,curlDownTransition几种。
****3.6 SDWebImageOptionsProcessor ****
全局Options处理器,用于针对所有的Options设置进行过滤处理。
****3.7 SDWebImageIndicator ****
进度指示器,目前主要支持两种:SDWebImageActivityIndicator和SDWebImageProgressIndicator