文章目录
- 前言
- 它提供了UIImageView的一个分类,支持从网路上下载且缓存图片,并设置图片到对应的UIImageView控件或者UIButton控件。
- SDWebImage简介
- 官方图解
- 主序列图(Main Sequence Disagram)
- 顶层API图(Top Level API Diagram)
- 整体类图(Overall Class Diagram)
- SDWebImage功能
- 核心类
- 概念框架
- 源码
- UIView+WebCache
- 取消之前下载操作
- 设置占位图
- 判断URL是否合法
- 工具层
- SDWebImageManager
- loadImageWithURL
- 先来看第一个,返回SDWebImageOptionsResult:
- 第二个callCacheProcessForOperation的调用
- 在这里调用了下载方法callDownloadProcessForOperation
- SDImageCache
- SDWebImageDownloader
前言
SDWebImage这个库提供了具有缓存支持的异步图像下载器。
它提供了UIImageView的一个分类,支持从网路上下载且缓存图片,并设置图片到对应的UIImageView控件或者UIButton控件。
提示:以下是本篇文章正文内容,下面案例可供参考
SDWebImage简介
- 提供了一个UIImageView的category用来加载网络图片并且对网络图片的缓存进行管理
- 采用异步方法来下载网络图片
- 采用异步的方法,使用内存+磁盘来缓存网络图片,拥有自动的缓存国旗处理机制
- 支持的图片格式包括PNG,JPEG,GIF,Webp,等
- 支持GIF动图(4.0之前的效果不太好,4.0后基于FLAnimatedImage加载动图)
- 支持后台的图片解压缩处理
- 支持Arm64
- 同一个url的图片不会被重复下载
- 失效,虚假的url不会被无限重试
- 耗时操作都在子线程,确保不阻塞主线程
- 使用GCD和ARC
官方图解
主序列图(Main Sequence Disagram)
webcache
Web缓存是指在Web浏览器或Web代理服务器中保存已经获取的Web资源(如HTML页面、CSS样式表、JavaScript代码、图像、视频、音频等)的一种技术。Web缓存通常是在本地磁盘或内存中进行的,目的是提高Web应用程序的性能、降低网络带宽的使用和减少Web服务器的负载。
Web缓存的工作原理如下:
当用户请求访问一个Web资源时,浏览器或代理服务器会首先检查本地缓存是否已经存在该资源。
如果本地缓存已经存在该资源,并且缓存资源仍然有效(即未过期),则浏览器或代理服务器会直接从本地缓存中获取资源,并将其返回给用户。
如果本地缓存不存在该资源,或者缓存资源已经过期,浏览器或代理服务器将向Web服务器发送请求,获取最新的资源并存储到本地缓存中。
Web缓存的好处包括:
减少网络带宽的使用:当Web资源被缓存在本地,用户在访问相同资源时无需从Web服务器下载,从而节省了网络带宽的使用。
提高Web应用程序的性能:当Web资源被缓存在本地,用户在访问相同资源时可以更快地加载,从而提高了Web应用程序的性能。
减轻Web服务器的负载:当Web资源被缓存在本地,用户在访问相同资源时无需从Web服务器下载,从而减轻了Web服务器的负载。
总的来说,Web缓存是一种非常重要的技术,它可以提高Web应用程序的性能、降低网络带宽的使用和减轻Web服务器的负载。
SDWebImageManager
SDWebImageManager是SDWebImage的核心管理器,它负责协调下载器和缓存器,并提供了一个简单的接口用于加载和取消图像的下载和缓存。它是一个单例对象,可以在整个应用程序中使用。
SDWebImageManager的主要功能包括:
下载图像:SDWebImageManager通过实例化下载器来下载图像,并将下载后的图像传递给缓存器进行缓存。它支持异步下载图像并提供回调机制。
缓存图像:SDWebImageManager可以将下载的图像缓存到磁盘和内存中,以便在以后的访问中更快地加载图像。
取消下载和缓存:SDWebImageManager允许取消正在下载和缓存的操作。
自定义缓存和下载策略:SDWebImageManager允许开发人员根据自己的需求自定义缓存和下载策略。
图像解码器:SDWebImageManager支持多种图像格式的解码器,并允许开发人员自定义图像解码器。
扩展:SDWebImageManager支持扩展,例如WebP格式和GIF动画格式。
SDWebImageManager是一个非常强大的图像加载和缓存框架,它可以帮助开发人员轻松地实现异步图像加载和缓存,并提高应用程序的性能。
顶层API图(Top Level API Diagram)
整体类图(Overall Class Diagram)
SDWebImage功能
核心类
- SDWebImageDownloader:负责维持图片的下载队列,是一个单例对象
- SDWebImageDownloaderOperation:负责真正的图片下载请求,一个自定义的并行Operation子类
- SDImageCache:负责SDWebImage的缓存工作,是一个单例对象
- SDWebImageManager:是总的管理类,维护了一个SDWebImageDownloader实例和一个SDImageCache实例,是下载与缓存的桥梁
- SDWebImagePrefetcher:负责图片的预取
- SDWebImageDecoder:负责图片的解压缩
- UIImageVuew+WebCache:和其他的扩展都是与用户直接打交道的
概念框架
- UIImageView+WebCache和UIButton+WebCache直接为表层的UIKit框架提供接口
- SDWebImageManager负责处理和协调SDWebImageDownloader和SDWebImagecache,并和UIKit层进行交互
- SDWebImageDownloaderOperation真正执行下载请求,最底层的两个类为高层抽象提供支持
源码
- (void)sd_setImageWithURL:(nullable NSURL *)url {[self sd_setImageWithURL:url placeholderImage:nil options:0 progress:nil completed:nil];
}- (void)sd_setImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder {[self sd_setImageWithURL:url placeholderImage:placeholder options:0 progress:nil completed:nil];
}- (void)sd_setImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder options:(SDWebImageOptions)options {[self sd_setImageWithURL:url placeholderImage:placeholder options:options progress:nil completed:nil];
}- (void)sd_setImageWithURL:(nullable NSURL *)url completed:(nullable SDExternalCompletionBlock)completedBlock {[self sd_setImageWithURL:url placeholderImage:nil options:0 progress:nil completed:completedBlock];
}- (void)sd_setImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder completed:(nullable SDExternalCompletionBlock)completedBlock {[self sd_setImageWithURL:url placeholderImage:placeholder options:0 progress:nil completed:completedBlock];
}
- (void)sd_setImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder options:(SDWebImageOptions)options completed:(nullable SDExternalCompletionBlock)completedBlock {[self sd_setImageWithURL:url placeholderImage:placeholder options:options progress:nil completed:completedBlock];
}
他们都调用了同一个方法
//UIImageView+WebCache
- (void)sd_setImageWithURL:(nullable NSURL *)url
placeholderImage:(nullable UIImage *)placeholder
options:(SDWebImageOptions)options
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDExternalCompletionBlock)completedBlock;
- (void)sd_setImageWithURL:(nullable NSURL *)urlplaceholderImage:(nullable UIImage *)placeholderoptions:(SDWebImageOptions)optionsprogress:(nullable SDWebImageDownloaderProgressBlock)progressBlockcompleted:(nullable SDExternalCompletionBlock)completedBlock {[self sd_internalSetImageWithURL:urlplaceholderImage:placeholderoptions:optionsoperationKey:nilsetImageBlock:nilprogress:progressBlockcompleted:completedBlock];
}
UIView+WebCache
- (void)sd_internalSetImageWithURL:(nullable NSURL *)urlplaceholderImage:(nullable UIImage *)placeholderoptions:(SDWebImageOptions)optionsoperationKey:(nullable NSString *)operationKeysetImageBlock:(nullable SDSetImageBlock)setImageBlockprogress:(nullable SDWebImageDownloaderProgressBlock)progressBlockcompleted:(nullable SDExternalCompletionBlock)completedBlock {return [self sd_internalSetImageWithURL:url placeholderImage:placeholder options:options operationKey:operationKey setImageBlock:setImageBlock progress:progressBlock completed:completedBlock context:nil];
}
//url。图像的url
//placeholder 最初要设置的图像,直到图像请求完成
//options 下载图像时要使用的选项
//context 上下文包含用于执行指定更改或过程的不同选项
//setImageBlock 块用于自定义设置图像代码
//progressBlock 下载图像时调用的块。进度块是在后台队列上执行的
//这个块没有返回值,并以请求的UIImage作为第一个参数,NSData表示作为第二个参数。如果发生错误,image参数为nil,第三个参数可能包含一个NSError。第四个参数是一个“SDImageCacheType”enum,表示图像是从本地缓存,内存缓存韩式网络检测到的。第五个参数通常是“YES”,但是,如果你提供SDWebImageAvoidAutoSetImage与SDWebImageProgressiveLoad选项,以启用渐进下载和设置自己的映像。因此,对部分图像重复调用该块。当图像完全下载后,最后一次调用该块,并将最后一个参数设置为YES。
//最后一个参数是原始图像URL。
- (void)sd_internalSetImageWithURL:(nullable NSURL *)urlplaceholderImage:(nullable UIImage *)placeholderoptions:(SDWebImageOptions)optionscontext:(nullable SDWebImageContext *)contextsetImageBlock:(nullable SDSetImageBlock)setImageBlockprogress:(nullable SDImageLoaderProgressBlock)progressBlockcompleted:(nullable SDInternalCompletionBlock)completedBlock {if (context) {//获取枚举选项// copy to avoid mutable object// 复制以避免可变对象context = [context copy];} else {context = [NSDictionary dictionary];}//获取一个有效的操作的对应键值,这里获取的是"设置图片操作的键"NSString *validOperationKey = context[SDWebImageContextSetImageOperationKey];//如果传递的context中没有该图片操作键,就新建一个该类的操作键赋给contextif (!validOperationKey) {// pass through the operation key to downstream, which can used for tracing operation or image view class// 将操作键传递给下游,可用于跟踪操作或图像视图类validOperationKey = NSStringFromClass([self class]);SDWebImageMutableContext *mutableContext = [context mutableCopy];mutableContext[SDWebImageContextSetImageOperationKey] = validOperationKey;context = [mutableContext copy];}//将当前操作键赋值给 sd_latestOperationKey属性,操作键用于标识一个视图实例的不同查询(如UIButton)。self.sd_latestOperationKey = validOperationKey;//下面这行代码是保证没有当前正在进行的异步下载操作, 使它不会与即将进行的操作发生冲突[self sd_cancelImageLoadOperationWithKey:validOperationKey];//将传递过来的url赋值给 sd_imageURL属性//注意,由于类别的限制,如果直接使用setImage:,这个属性可能会不同步。self.sd_imageURL = url;//添加临时的占位图(在不延迟添加占位图的option下)if (!(options & SDWebImageDelayPlaceholder)) {dispatch_main_async_safe(^{[self sd_setImage:placeholder imageData:nil basedOnClassOrViaCustomSetImageBlock:setImageBlock cacheType:SDImageCacheTypeNone imageURL:url];});}if (url) {// reset the progress//重置进程NSProgress *imageProgress = objc_getAssociatedObject(self, @selector(sd_imageProgress));if (imageProgress) {imageProgress.totalUnitCount = 0;imageProgress.completedUnitCount = 0;}#if SD_UIKIT || SD_MAC //环境判断// check and start image indicator// 检查并启动图像指示灯[self sd_startImageIndicator]; //开启图像指示器id<SDWebImageIndicator> imageIndicator = self.sd_imageIndicator;//self.sd_imageIndicator//图像加载期间的图像指示器。如果你不需要指示器,请指定nil。默认为零//这个设置将移除旧的指示器视图,并将新的指示器视图添加到当前视图的子视图中。//@注意,因为这是UI相关的,你应该只从主队列访问。#endif //环境判断//从context字典中获取该键对应的数据//SDWebImageContextOption const SDWebImageContextCustomManager = @"customManager";SDWebImageManager *manager = context[SDWebImageContextCustomManager];//如果不存在,就新创建一个单例if (!manager) {manager = [SDWebImageManager sharedManager];} else {// remove this manager to avoid retain cycle (manger -> loader -> operation -> context -> manager)// 删除manager以避免循环引用(manger -> loader -> operation -> context -> manager)SDWebImageMutableContext *mutableContext = [context mutableCopy];mutableContext[SDWebImageContextCustomManager] = nil;context = [mutableContext copy];}//--------------SDImageLoaderProgressBlock-----------------//SDImageLoaderProgressBlock combinedProgressBlock = ^(NSInteger receivedSize, NSInteger expectedSize, NSURL * _Nullable targetURL) {if (imageProgress) {//项目总数imageProgress.totalUnitCount = expectedSize;//已完成数imageProgress.completedUnitCount = receivedSize;}
#if SD_UIKIT || SD_MAC//环境判断//重写并更新进度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];});}
#endif //环境判断if (progressBlock) {progressBlock(receivedSize, expectedSize, targetURL);}};//--------------SDImageLoaderProgressBlock-----------------//@weakify(self);//SDWebImageManager下载图片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 SD_UIKIT || SD_MAC// check and stop image indicator// 检查和停止图像指示灯,如果完成了就停止if (finished) {[self sd_stopImageIndicator];}
#endif//是否应该调用CompletedBlock,通过options和状态的&运算决定BOOL shouldCallCompletedBlock = finished || (options & SDWebImageAvoidAutoSetImage);//是否应该不设置Image,通过options和状态的&运算决定BOOL shouldNotSetImage = ((image && (options & SDWebImageAvoidAutoSetImage)) ||(!image && !(options & SDWebImageDelayPlaceholder)));//调用CompletedBlock的BlockSDWebImageNoParamsBlock callCompletedBlockClojure = ^{if (!self) { return; }if (!shouldNotSetImage) {[self sd_setNeedsLayout];}//判断是否需要调用completedBlockif (completedBlock && shouldCallCompletedBlock) {//调用completedBlock,image,而且不自动替换 placeholder imagecompletedBlock(image, data, error, cacheType, finished, url);}};// case 1a: we got an image, but the SDWebImageAvoidAutoSetImage flag is set;我们得到了一个图像,但是SDWebImageAvoidAutoSetImage标志被设置了// OR// case 1b: we got no image and the SDWebImageDelayPlaceholder is not set;我们没有得到图像,SDWebImageDelayPlaceholder没有设置if (shouldNotSetImage) { //如果应该不设置图像//主线程不等待安全的调用上述创建的callCompletedBlockClojuredispatch_main_async_safe(callCompletedBlockClojure);return;//结束函数}UIImage *targetImage = nil;//目标图像NSData *targetData = nil;//目标数据if (image) {// case 2a: we got an image and the SDWebImageAvoidAutoSetImage is not set//我们得到一个图像,SDWebImageAvoidAutoSetImage没有设置targetImage = image;targetData = data;} else if (options & SDWebImageDelayPlaceholder) {// case 2b: we got no image and the SDWebImageDelayPlaceholder flag is set//我们没有图像,并且设置了SDWebImageDelayPlaceholder标志targetImage = placeholder;targetData = nil;}#if SD_UIKIT || SD_MAC// 检查是否使用图像过渡SDWebImageTransition *transition = nil;BOOL shouldUseTransition = NO;//是否使用过滤属性if (options & SDWebImageForceTransition) {//强制转换// 总是shouldUseTransition = YES;} else if (cacheType == SDImageCacheTypeNone) { //类型不可用// 从网络shouldUseTransition = YES;} else {// From disk (and, user don't use sync query)// 从磁盘(并且,用户不使用同步查询)if (cacheType == SDImageCacheTypeMemory) { //内存shouldUseTransition = NO;} else if (cacheType == SDImageCacheTypeDisk) { //磁盘if (options & SDWebImageQueryMemoryDataSync || options & SDWebImageQueryDiskDataSync) {shouldUseTransition = NO;} else {shouldUseTransition = YES;}} else {// Not valid cache type, fallback// 缓存类型无效,请回退shouldUseTransition = NO;}}//完成并且应该使用转换if (finished && shouldUseTransition) {transition = self.sd_imageTransition; //转换属性,图片加载完成后的图片转换}
#endif//dispatch_main_async_safe : 保证block能在主线程安全进行,不等待dispatch_main_async_safe(^{
#if SD_UIKIT || SD_MAC//马上替换 placeholder image[self sd_setImage:targetImage imageData:targetData basedOnClassOrViaCustomSetImageBlock:setImageBlock transition:transition cacheType:cacheType imageURL:imageURL];
#else//马上替换 placeholder image[self sd_setImage:targetImage imageData:targetData basedOnClassOrViaCustomSetImageBlock:setImageBlock cacheType:cacheType imageURL:imageURL];
#endif//使用上述创建的block变量,调用CompletedBlockcallCompletedBlockClojure();});}];//----------operation----------////在操作缓存字典(operationDictionary)里添加operation,表示当前的操作正在进行[self sd_setImageLoadOperation:operation forKey:validOperationKey];//-------------------------url存在---------------------------//} else {
//-------------------------url不存在---------------------------//
#if SD_UIKIT || SD_MAC//url不存在立马停止图像指示器[self sd_stopImageIndicator];
#endif//如果url不存在,就在completedBlock里传入error(url为空)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);}});}//-------------------------url不存在---------------------------//
}
取消之前下载操作
if (context) {//获取枚举选项// copy to avoid mutable object// 复制以避免可变对象context = [context copy];} else {context = [NSDictionary dictionary];}//获取一个有效的操作的对应键值,这里获取的是"设置图片操作的键"NSString *validOperationKey = context[SDWebImageContextSetImageOperationKey];//如果传递的context中没有该图片操作键,就新建一个该类的操作键赋给contextif (!validOperationKey) {// pass through the operation key to downstream, which can used for tracing operation or image view class// 将操作键传递给下游,可用于跟踪操作或图像视图类validOperationKey = NSStringFromClass([self class]);SDWebImageMutableContext *mutableContext = [context mutableCopy];mutableContext[SDWebImageContextSetImageOperationKey] = validOperationKey;context = [mutableContext copy];}//将当前操作键赋值给 sd_latestOperationKey属性,操作键用于标识一个视图实例的不同查询(如UIButton)。self.sd_latestOperationKey = validOperationKey;//下面这行代码是保证没有当前正在进行的异步下载操作, 使它不会与即将进行的操作发生冲突[self sd_cancelImageLoadOperationWithKey:validOperationKey];
其中在这一部分调用了UIView+WebCacheOperation中的方法,我们来看看它是怎么实现的:
//设置图像加载操作,使用一个字典存储key和对应的操作
- (void)sd_setImageLoadOperation:(nullable id<SDWebImageOperation>)operation forKey:(nullable NSString *)key {//key存在if (key) {//取消当前UIView和key的所有操作,key——标识操作的键[self sd_cancelImageLoadOperationWithKey:key];//操作存在if (operation) {//获取operationDictionary字典SDOperationsDictionary *operationDictionary = [self sd_operationDictionary];//创建一个互斥锁,保证此时没有其它线程对self对象进行修改,然后再添加key和对应的operation(操作)@synchronized (self) {[operationDictionary setObject:operation forKey:key];}}}
}- (void)sd_cancelImageLoadOperationWithKey:(nullable NSString *)key {if (key) {// Cancel in progress downloader from queueSDOperationsDictionary *operationDictionary = [self sd_operationDictionary];id<SDWebImageOperation> operation;@synchronized (self) {operation = [operationDictionary objectForKey:key];}if (operation) {if ([operation conformsToProtocol:@protocol(SDWebImageOperation)]) {[operation cancel];}@synchronized (self) {[operationDictionary removeObjectForKey:key];}}}
}
实际上,所有的操作都是由一个实际上,所有的操作都是由一个operationDictionary字典维护的,执行新的操作之前,cancel所有的operation。
为什么不直接在UIImageView+WebCache里直接关联这个对象呢?我觉得这里作者应该是遵从面向对象的单一职责原则(SRP:Single responsibility principle),就连类都要履行这个职责,何况分类呢?这里作者专门创造一个分类UIView+WebCacheOperation来管理操作缓存(字典)。
设置占位图
//2.设置占位图//将传递过来的url赋值给 sd_imageURL属性//注意,由于类别的限制,如果直接使用setImage:,这个属性可能会不同步。self.sd_imageURL = url;//添加临时的占位图(在不延迟添加占位图的option下)if (!(options & SDWebImageDelayPlaceholder)) {dispatch_main_async_safe(^{[self sd_setImage:placeholder imageData:nil basedOnClassOrViaCustomSetImageBlock:setImageBlock cacheType:SDImageCacheTypeNone imageURL:url];});}
判断URL是否合法
//3.判断URL是否合法if (url) {// reset the progress//重置进程NSProgress *imageProgress = objc_getAssociatedObject(self, @selector(sd_imageProgress));if (imageProgress) {imageProgress.totalUnitCount = 0;imageProgress.completedUnitCount = 0;}#if SD_UIKIT || SD_MAC //环境判断// check and start image indicator// 检查并启动图像指示灯[self sd_startImageIndicator]; //开启图像指示器id<SDWebImageIndicator> imageIndicator = self.sd_imageIndicator;//self.sd_imageIndicator//图像加载期间的图像指示器。如果你不需要指示器,请指定nil。默认为零//这个设置将移除旧的指示器视图,并将新的指示器视图添加到当前视图的子视图中。//@注意,因为这是UI相关的,你应该只从主队列访问。#endif //环境判断//从context字典中获取该键对应的数据//SDWebImageContextOption const SDWebImageContextCustomManager = @"customManager";SDWebImageManager *manager = context[SDWebImageContextCustomManager];//如果不存在,就新创建一个单例if (!manager) {manager = [SDWebImageManager sharedManager];} else {// remove this manager to avoid retain cycle (manger -> loader -> operation -> context -> manager)// 删除manager以避免循环引用(manger -> loader -> operation -> context -> manager)SDWebImageMutableContext *mutableContext = [context mutableCopy];mutableContext[SDWebImageContextCustomManager] = nil;context = [mutableContext copy];}//--------------SDImageLoaderProgressBlock-----------------//SDImageLoaderProgressBlock combinedProgressBlock = ^(NSInteger receivedSize, NSInteger expectedSize, NSURL * _Nullable targetURL) {if (imageProgress) {//项目总数imageProgress.totalUnitCount = expectedSize;//已完成数imageProgress.completedUnitCount = receivedSize;}
#if SD_UIKIT || SD_MAC//环境判断//重写并更新进度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];});}
#endif //环境判断if (progressBlock) {progressBlock(receivedSize, expectedSize, targetURL);}};//--------------SDImageLoaderProgressBlock-----------------//@weakify(self);//SDWebImageManager下载图片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 SD_UIKIT || SD_MAC// check and stop image indicator// 检查和停止图像指示灯,如果完成了就停止if (finished) {[self sd_stopImageIndicator];}
#endif//是否应该调用CompletedBlock,通过options和状态的&运算决定BOOL shouldCallCompletedBlock = finished || (options & SDWebImageAvoidAutoSetImage);//是否应该不设置Image,通过options和状态的&运算决定BOOL shouldNotSetImage = ((image && (options & SDWebImageAvoidAutoSetImage)) ||(!image && !(options & SDWebImageDelayPlaceholder)));//调用CompletedBlock的BlockSDWebImageNoParamsBlock callCompletedBlockClojure = ^{if (!self) { return; }if (!shouldNotSetImage) {[self sd_setNeedsLayout];}//判断是否需要调用completedBlockif (completedBlock && shouldCallCompletedBlock) {//调用completedBlock,image,而且不自动替换 placeholder imagecompletedBlock(image, data, error, cacheType, finished, url);}};// case 1a: we got an image, but the SDWebImageAvoidAutoSetImage flag is set;我们得到了一个图像,但是SDWebImageAvoidAutoSetImage标志被设置了// OR// case 1b: we got no image and the SDWebImageDelayPlaceholder is not set;我们没有得到图像,SDWebImageDelayPlaceholder没有设置if (shouldNotSetImage) { //如果应该不设置图像//主线程不等待安全的调用上述创建的callCompletedBlockClojuredispatch_main_async_safe(callCompletedBlockClojure);return;//结束函数}UIImage *targetImage = nil;//目标图像NSData *targetData = nil;//目标数据if (image) {// case 2a: we got an image and the SDWebImageAvoidAutoSetImage is not set//我们得到一个图像,SDWebImageAvoidAutoSetImage没有设置targetImage = image;targetData = data;} else if (options & SDWebImageDelayPlaceholder) {// case 2b: we got no image and the SDWebImageDelayPlaceholder flag is set//我们没有图像,并且设置了SDWebImageDelayPlaceholder标志targetImage = placeholder;targetData = nil;}#if SD_UIKIT || SD_MAC// 检查是否使用图像过渡SDWebImageTransition *transition = nil;BOOL shouldUseTransition = NO;//是否使用过滤属性if (options & SDWebImageForceTransition) {//强制转换// 总是shouldUseTransition = YES;} else if (cacheType == SDImageCacheTypeNone) { //类型不可用// 从网络shouldUseTransition = YES;} else {// From disk (and, user don't use sync query)// 从磁盘(并且,用户不使用同步查询)if (cacheType == SDImageCacheTypeMemory) { //内存shouldUseTransition = NO;} else if (cacheType == SDImageCacheTypeDisk) { //磁盘if (options & SDWebImageQueryMemoryDataSync || options & SDWebImageQueryDiskDataSync) {shouldUseTransition = NO;} else {shouldUseTransition = YES;}} else {// Not valid cache type, fallback// 缓存类型无效,请回退shouldUseTransition = NO;}}//完成并且应该使用转换if (finished && shouldUseTransition) {transition = self.sd_imageTransition; //转换属性,图片加载完成后的图片转换}
#endif//dispatch_main_async_safe : 保证block能在主线程安全进行,不等待dispatch_main_async_safe(^{
#if SD_UIKIT || SD_MAC//马上替换 placeholder image[self sd_setImage:targetImage imageData:targetData basedOnClassOrViaCustomSetImageBlock:setImageBlock transition:transition cacheType:cacheType imageURL:imageURL];
#else//马上替换 placeholder image[self sd_setImage:targetImage imageData:targetData basedOnClassOrViaCustomSetImageBlock:setImageBlock cacheType:cacheType imageURL:imageURL];
#endif//使用上述创建的block变量,调用CompletedBlockcallCompletedBlockClojure();});}];//----------operation----------////在操作缓存字典(operationDictionary)里添加operation,表示当前的操作正在进行[self sd_setImageLoadOperation:operation forKey:validOperationKey];//-------------------------url存在---------------------------//} else {
//-------------------------url不存在---------------------------//
#if SD_UIKIT || SD_MAC//url不存在立马停止图像指示器[self sd_stopImageIndicator];
#endif//如果url不存在,就在completedBlock里传入error(url为空)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);}});}//-------------------------url不存在---------------------------//
工具层
上文提到过,SDWebImageManager同时管理SDImageCache和SDWebImageDownloader两个类,它是这一层的老大哥。在下载任务开始的时候,SDWebImageManager首先访问SDImageCache来查询是否存在缓存,如果有缓存,直接返回缓存的图片。如果没有缓存,就命令SDWebImageDownloader来下载图片,下载成功后,存入缓存,显示图片。以上是SDWebImageManager大致的工作流程。
直接找到loadImageWithURL方法,这个方法主要是对url的一些判断,context与options的预处理,内容如下:
a. 先判断url的可行性
b. 对context,options进行预处理,并放到result里面
c. 调用callCacheProcessForOperation 判断是否有缓存,如果有则进入ImageCache 拿到缓存数据,如果没有则进入callDownloadProcessForOperation 方法进一步判断如何下载
先看看这些步骤的源码,看完再看callCacheProcessForOperation做了些什么
SDWebImageManager
// ============== SDWebImageManager.m的扩展属性声明部分 ============== //
@property (strong, nonatomic, readwrite, nonnull) SDImageCache *imageCache; //管理缓存
@property (strong, nonatomic, readwrite, nonnull) id<SDImageLoader> imageLoader; //下载器imageLoader;
@property (strong, nonatomic, nonnull) NSMutableSet<NSURL *> *failedURLs; //记录失效url的名单
@property (strong, nonatomic, nonnull) NSMutableSet<SDWebImageCombinedOperation *> *runningOperations; //记录当前正在执行的操作
- (nullable SDWebImageCombinedOperation *)loadImageWithURL:(nullable NSURL *)urloptions:(SDWebImageOptions)optionscontext:(nullable SDWebImageContext *)contextprogress:(nullable SDImageLoaderProgressBlock)progressBlockcompleted:(nonnull SDInternalCompletionBlock)completedBlock;
//如果缓存中没有图像,则在给定URL下载图像,否则返回缓存版本。
//参数一:url 图像的URL
//参数二:options 指定此请求使用的选项的掩码
//参数三:context 上下文包含不同的选项来执行指定的更改或流程,参见' SDWebImageContextOption '。这将保存'options'枚举不能保存的额外对象。
//参数四:progressBlock 下载图像时调用的块,进度块在后台队列上执行
//参数五:completedBlock 当操作完成时调用的块//必选参数/* 这个块没有返回值,并以请求的UIImage作为第一个参数,NSData表示作为第二个参数。* 如果发生错误,image参数为nil,第三个参数可能包含一个NSError。* 第四个参数是一个“SDImageCacheType”枚举,指示是否从本地缓存检索到图像或者从内存缓存或者网络。* 当使用SDWebImageProgressiveLoad选项并下载镜像时,第五个参数设置为NO。因此,对部分图像重复调用该块。当图像完全下载后,最后一次调用该块,并将最后一个参数设置为YES。* 最后一个参数是原始图像URL//return 返回一个SDWebImageCombinedOperation的实例,你可以取消加载过程。
loadImageWithURL
- (SDWebImageCombinedOperation *)loadImageWithURL:(nullable NSURL *)urloptions:(SDWebImageOptions)optionscontext:(nullable SDWebImageContext *)contextprogress:(nullable SDImageLoaderProgressBlock)progressBlockcompleted:(nonnull SDInternalCompletionBlock)completedBlock {// Invoking this method without a completedBlock is pointless// 没有completedBlock调用这个方法是没有意义的NSAssert(completedBlock != nil, @"If you mean to prefetch the image, use -[SDWebImagePrefetcher prefetchURLs] instead");// Very common mistake is to send the URL using NSString object instead of NSURL. For some strange reason, Xcode won't// throw any warning for this type mismatch. Here we failsafe this error by allowing URLs to be passed as NSString.//很常见的错误是使用NSString对象而不是NSURL发送URL。由于一些奇怪的原因,Xcode不会对这种类型不匹配抛出任何警告。这里我们通过允许url作为NSString传递来避免这个错误。//如果url是NSString类,那么就将它转为NSURL类if ([url isKindOfClass:NSString.class]) {url = [NSURL URLWithString:(NSString *)url];}// Prevents app crashing on argument type error like sending NSNull instead of NSURL// 防止应用程序在参数类型错误时崩溃,比如发送NSNull而不是NSURL//如果url不是NSURL类那么就赋值为nilif (![url isKindOfClass:NSURL.class]) {url = nil;}//SDWebImageCombinedOperation表示缓存和加载器操作的组合操作。您可以使用它来取消加载过程。SDWebImageCombinedOperation *operation = [SDWebImageCombinedOperation new];//让上述变量的manager指向该类operation.manager = self;//判断该URL是否在黑名单中BOOL isFailedUrl = NO;//如果url存在if (url) {//线程等待self.failedURLsLock执行,知道其执行完毕SD_LOCK(self.failedURLsLock);//在self.failedURLs(失效的url名单)中查找是否有这个urlisFailedUrl = [self.failedURLs containsObject:url];//使传入的信号量self.failedURLsLock的调用值加1,表示当前又加入一个线程等待其处理的信号量SD_UNLOCK(self.failedURLsLock);}//url的绝对字符串长度为0或者(不禁用黑名单)并且Url在黑名单内,发出警告提示//url有问题的话该函数就在这就结束了if (url.absoluteString.length == 0 || (!(options & SDWebImageRetryFailed) && isFailedUrl)) {NSString *description = isFailedUrl ? @"Image url is blacklisted" : @"Image url is nil";NSInteger code = isFailedUrl ? SDWebImageErrorBlackListed : SDWebImageErrorInvalidURL;//调用completionBlock块,结束该函数[self callCompletionBlockForOperation:operation completion:completedBlock error:[NSError errorWithDomain:SDWebImageErrorDomain code:code userInfo:@{NSLocalizedDescriptionKey : description}] url:url];return operation;}//将这个操作加入进程SD_LOCK(self.runningOperationsLock);[self.runningOperations addObject:operation];SD_UNLOCK(self.runningOperationsLock);// Preprocess the options and context arg to decide the final the result for manager// 对操作和上下文参数进行预处理,为manager确定最终结果//返回已处理的操作结果,包括指定图像URL的操作和contextSDWebImageOptionsResult *result = [self processedResultForURL:url options:options context:context];// Start the entry to load image from cache// 启动从缓存加载图像的条目//查询普通缓存进程[self callCacheProcessForOperation:operation url:url options:result.options context:result.context progress:progressBlock completed:completedBlock];//返回这个新创建的操作变量return operation;
}
在最后两步调用了两个方法:- (SDWebImageOptionsResult *)processedResultForURL:(NSURL *)url options:(SDWebImageOptions)options context:(SDWebImageContext *)context;(返回已处理的操作结果,包括指定图像URL的操作和content)和- (void)callCacheProcessForOperation:(nonnull SDWebImageCombinedOperation *)operation url:(nonnull NSURL *)url options:(SDWebImageOptions)options context:(nullable SDWebImageContext *)context progress:(nullable SDImageLoaderProgressBlock)progressBlock completed:(nullable SDInternalCompletionBlock)completedBlock;(查询普通缓存进程)这个函数中才开始真正下载此URL中的内容。
先来看第一个,返回SDWebImageOptionsResult:
这里先将任务加入到正在执行的列表里面,然后再对context进行预处理,源代码是没有对options进行说明处理的,然后将context跟options放入result里面。context的处理源代码就不贴出来了,大概就是对SDWebImageContextImageTransformer、SDWebImageContextCacheKeyFilter、SDWebImageContextCacheSerializer这3个进行一个判断,看是否有自定义的传过来,没有就用默认的。
- (SDWebImageOptionsResult *)processedResultForURL:(NSURL *)url options:(SDWebImageOptions)options context:(SDWebImageContext *)context {SDWebImageOptionsResult *result;SDWebImageMutableContext *mutableContext = [SDWebImageMutableContext dictionary];// Image Transformer from manager// 来自管理器的图像转换器if (!context[SDWebImageContextImageTransformer]) {id<SDImageTransformer> transformer = self.transformer;[mutableContext setValue:transformer forKey:SDWebImageContextImageTransformer];}// Cache key filter from manager// 从管理器缓存密钥过滤器if (!context[SDWebImageContextCacheKeyFilter]) {id<SDWebImageCacheKeyFilter> cacheKeyFilter = self.cacheKeyFilter;[mutableContext setValue:cacheKeyFilter forKey:SDWebImageContextCacheKeyFilter];}// Cache serializer from manager// 从管理器缓存序列化器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];}// Apply options processor// 应用操作处理器if (self.optionsProcessor) {result = [self.optionsProcessor processedResultForURL:url options:options context:context];}if (!result) {// Use default options result// 使用默认操作结果result = [[SDWebImageOptionsResult alloc] initWithOptions:options context:context];}return result;
}
第二个callCacheProcessForOperation的调用
这里主要是判断要到哪里去取数据,ImageCache,还是去下载,接下来就进入这个方法看一下。
这里主要是判断任务是否该走缓存查询,或者直接下载。如果是缓存查询,就进入SDImageCache里面进行缓存查询,且在此处理缓存结果的回调。否则就调用callDownloadProcessForOperation进入下一步判断。
①. 拿到imageCache,拿到缓存类型queryCacheType
②. 通过 options判断,走缓存还是下载。如果走缓存,则调用SDImageCache里面的queryImageForKey(开始进入SDImageCache的逻辑);如果走下载,则调用callDownloadProcessForOperation开始下载前的一些处理。
- (void)callCacheProcessForOperation:(nonnull SDWebImageCombinedOperation *)operationurl:(nonnull NSURL *)urloptions:(SDWebImageOptions)optionscontext:(nullable SDWebImageContext *)contextprogress:(nullable SDImageLoaderProgressBlock)progressBlockcompleted:(nullable SDInternalCompletionBlock)completedBlock {// Grab the image cache to use// 获取要使用的图像缓存id<SDImageCache> imageCache;//判断SDWebImageContextImageCache对应内容是否符合协议SDImageCacheif ([context[SDWebImageContextImageCache] conformsToProtocol:@protocol(SDImageCache)]) {imageCache = context[SDWebImageContextImageCache];} else {imageCache = self.imageCache;}// Get the query cache type// 获取查询缓存类型,存在的话就将其转换为integer类型数据SDImageCacheType queryCacheType = SDImageCacheTypeAll;if (context[SDWebImageContextQueryCacheType]) {queryCacheType = [context[SDWebImageContextQueryCacheType] integerValue];}// Check whether we should query cache// 检查是否需要查询缓存BOOL shouldQueryCache = !SD_OPTIONS_CONTAINS(options, SDWebImageFromLoaderOnly);//需要查询缓存if (shouldQueryCache) {//将url和context结合起来转换成一个cache的keyNSString *key = [self cacheKeyForURL:url context:context];@weakify(operation);//定义一个弱引用self的变量,用于下面的block,避免循环引用//开始查询operation.cacheOperation = [imageCache queryImageForKey:key options:options context:context cacheType:queryCacheType completion:^(UIImage * _Nullable cachedImage, NSData * _Nullable cachedData, SDImageCacheType cacheType) {@strongify(operation);//定义一个强引用self的变量//如果operation存在并且operation被取消了if (!operation || operation.isCancelled) {// Image combined operation cancelled by user// 用户取消图像组合操作,调用CompletionBlock块,做出相应提示,说明用户取消了图像组合操作[self callCompletionBlockForOperation:operation completion:completedBlock error:[NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorCancelled userInfo:@{NSLocalizedDescriptionKey : @"Operation cancelled by user during querying the cache"}] url:url];//将该operation安全移除进程,然后结束函数[self safelyRemoveOperationFromRunning:operation];return;} else if (context[SDWebImageContextImageTransformer] && !cachedImage) {//SDWebImageContextImageTransformer对应内容存在并且cachedImage不存在// 有机会查询原始缓存而不是下载,在原始缓存中查找// Have a chance to query original cache instead of downloading[self callOriginalCacheProcessForOperation:operation url:url options:options context:context progress:progressBlock completed:completedBlock];return;}// Continue download process// 继续下载过程,这里的下载不同于下面的下载,这里的下载是从缓存中下载的,而不是url中[self callDownloadProcessForOperation:operation url:url options:options context:context cachedImage:cachedImage cachedData:cachedData cacheType:cacheType progress:progressBlock completed:completedBlock];}];} else {//不需要查询缓存// 继续下载过程,这里的下载是直接从url中下载的// Continue download process[self callDownloadProcessForOperation:operation url:url options:options context:context cachedImage:nil cachedData:nil cacheType:SDImageCacheTypeNone progress:progressBlock completed:completedBlock];}
}
在这里调用了下载方法callDownloadProcessForOperation
- (void)callDownloadProcessForOperation:(nonnull SDWebImageCombinedOperation *)operationurl:(nonnull NSURL *)urloptions:(SDWebImageOptions)optionscontext:(SDWebImageContext *)contextcachedImage:(nullable UIImage *)cachedImagecachedData:(nullable NSData *)cachedDatacacheType:(SDImageCacheType)cacheTypeprogress:(nullable SDImageLoaderProgressBlock)progressBlockcompleted:(nullable SDInternalCompletionBlock)completedBlock {// Grab the image loader to use// 获取要使用的图像加载器id<SDImageLoader> imageLoader;if ([context[SDWebImageContextImageLoader] conformsToProtocol:@protocol(SDImageLoader)]) {imageLoader = context[SDWebImageContextImageLoader];} else {imageLoader = self.imageLoader;}// Check whether we should download image from network// 检查是否需要从网络下载图像BOOL shouldDownload = !SD_OPTIONS_CONTAINS(options, SDWebImageFromCacheOnly);//(没有缓存图片) || (即使有缓存图片,也需要更新缓存图片)shouldDownload &= (!cachedImage || options & SDWebImageRefreshCached);//(代理没有响应imageManager:shouldDownloadImageForURL:消息,默认返回yes,需要下载图片)|| (imageManager:shouldDownloadImageForURL:返回yes,需要下载图片)shouldDownload &= (![self.delegate respondsToSelector:@selector(imageManager:shouldDownloadImageForURL:)] || [self.delegate imageManager:self shouldDownloadImageForURL:url]);shouldDownload &= [imageLoader canRequestImageForURL:url];//如果应该从url下载图像if (shouldDownload) {//存在缓存图片 && 即使有缓存图片也要下载更新图片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.// 如果在缓存中找到了图像,但是sdwebimagerfreshcached被提供了,通知缓存的图像// 并尝试重新下载它,以便NSURLCache有机会从服务器刷新它。[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 ========== //下载操作operation.loaderOperation = [imageLoader requestImageWithURL:url options:options context:context progress:progressBlock completed:^(UIImage *downloadedImage, NSData *downloadedData, NSError *error, BOOL finished) {@strongify(operation);//如果操作不存在并且任务被取消,则什么都不做,避免和其他的completedBlock重复if (!operation || operation.isCancelled) {// 用户取消图像组合操作// Image combined operation cancelled by user[self callCompletionBlockForOperation:operation completion:completedBlock error:[NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorCancelled userInfo:@{NSLocalizedDescriptionKey : @"Operation cancelled by user during sending the request"}] url:url];//存在缓存图片 && 即使有缓存图片也要下载更新图片 && 错误域相同 && error的代码为远程位置指定缓存的映像不被修改} else if (cachedImage && options & SDWebImageRefreshCached && [error.domain isEqualToString:SDWebImageErrorDomain] && error.code == SDWebImageErrorCacheNotModified) {// Image refresh hit the NSURLCache cache, do not call the completion block// 图像刷新击中NSURLCache缓存,不调用完成块//错误域相同 && 镜像加载操作在完成之前取消,在异步磁盘缓存查询期间,或在实际网络请求之前等待} else if ([error.domain isEqualToString:SDWebImageErrorDomain] && error.code == SDWebImageErrorCancelled) {// Download operation cancelled by user before sending the request, don't block failed URL// 下载操作被用户在发送请求前取消,不要阻止失败的URL[self callCompletionBlockForOperation:operation completion:completedBlock error:error url:url];//如果错误存在} else if (error) {[self callCompletionBlockForOperation:operation completion:completedBlock error:error url:url];//是否应该添加该url到错误名单中BOOL shouldBlockFailedURL = [self shouldBlockFailedURLWithURL:url error:error options:options context:context];//在错误url名单中添加当前的urlif (shouldBlockFailedURL) {SD_LOCK(self.failedURLsLock);[self.failedURLs addObject:url];SD_UNLOCK(self.failedURLsLock);}//下载成功} else {//如果需要下载失败后重新下载,则将当前url从失败url名单里移除if ((options & SDWebImageRetryFailed)) {SD_LOCK(self.failedURLsLock);[self.failedURLs removeObject:url];SD_UNLOCK(self.failedURLsLock);}// Continue store cache process// 继续存储缓存进程[self callStoreCacheProcessForOperation:operation url:url options:options context:context downloadedImage:downloadedImage downloadedData:downloadedData finished:finished progress:progressBlock completed:completedBlock];}//如果完成,从当前运行的操作列表里移除当前操作if (finished) {[self safelyRemoveOperationFromRunning:operation];}}];// ========== operation.loaderOperation ========== ////存在缓存图片} else if (cachedImage) {//调用完成的block[self callCompletionBlockForOperation:operation completion:completedBlock image:cachedImage data:cachedData error:nil cacheType:cacheType finished:YES url:url];//删去当前的的下载操作(线程安全)[self safelyRemoveOperationFromRunning:operation];//没有缓存的图片,而且下载被代理终止了} else {// Image not in cache and download disallowed by delegate// 调用完成的block[self callCompletionBlockForOperation:operation completion:completedBlock image:nil data:nil error:nil cacheType:SDImageCacheTypeNone finished:YES url:url];//删去当前的下载操作[self safelyRemoveOperationFromRunning:operation];}
}
SDImageCache
// ============== SDImageCache.m ============== //
@property (nonatomic, strong, readwrite, nonnull) id<SDMemoryCache> memoryCache;//内存缓存
@property (nonatomic, copy, readwrite, nonnull) NSString *diskCachePath;//磁盘缓存路径
@property (nonatomic, strong, nullable) dispatch_queue_t ioQueue;//ioQueue唯一子线程;
(nullable NSOperation *)queryCacheOperationForKey:(nullable NSString *)key options:(SDImageCacheOptions)options context:(nullable SDWebImageContext *)context cacheType:(SDImageCacheType)queryCacheType done:(nullable SDImageCacheQueryCompletionBlock)doneBlock {//key不存在,结束查找函数if (!key) {if (doneBlock) {doneBlock(nil, nil, SDImageCacheTypeNone);}return nil;}// Invalid cache type// 无效的缓存类型,结束查找函数if (queryCacheType == SDImageCacheTypeNone) {if (doneBlock) {doneBlock(nil, nil, SDImageCacheTypeNone);}return nil;}// First check the in-memory cache...//================查看内存的缓存=================//UIImage *image;//如果查询缓存类型不是只处理磁盘缓存,if (queryCacheType != SDImageCacheTypeDisk) {//在内存中找image = [self imageFromMemoryCacheForKey:key];}// 如果存在,直接调用block,将image,data,CaheType传进去if (image) {//如果操作为解码获取第一帧产生静态图像if (options & SDImageCacheDecodeFirstFrameOnly) {// Ensure static image// 确保静态图像Class animatedImageClass = image.class;//如果图像是动画 || animatedImageClass是UIImage的实例 && animatedImageClass遵循SDAnimatedImage协议if (image.sd_isAnimated || ([animatedImageClass isSubclassOfClass:[UIImage class]] && [animatedImageClass conformsToProtocol:@protocol(SDAnimatedImage)])) {
#if SD_MACimage = [[NSImage alloc] initWithCGImage:image.CGImage scale:image.scale orientation:kCGImagePropertyOrientationUp];
#elseimage = [[UIImage alloc] initWithCGImage:image.CGImage scale:image.scale orientation:image.imageOrientation];
#endif}//操作为确保总是与提供的类产生图像} 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 = (queryCacheType == SDImageCacheTypeMemory) || (image && !(options & SDImageCacheQueryMemoryData));if (shouldQueryMemoryOnly) {if (doneBlock) {doneBlock(image, nil, SDImageCacheTypeMemory);}//因为图片有缓存可供使用,所以不用实例化NSOperation,直接返回nilreturn nil;}// Second check the disk cache...//================查看磁盘的缓存=================//NSOperation *operation = [NSOperation new];// Check whether we need to synchronously query disk// 1. in-memory cache hit & memoryDataSync// 2. in-memory cache miss & diskDataSync// 检查是否需要同步查询磁盘//(在内存缓存命中) && memoryDataSync//(内存缓存错过) && diskDataSyncBOOL shouldQueryDiskSync = ((image && options & SDImageCacheQueryMemoryDataSync) ||(!image && options & SDImageCacheQueryDiskDataSync));void(^queryDiskBlock)(void) = ^{// 在用之前就判断operation是否被取消了,作者考虑的非常严谨,如果取消了就直接结束该函数了if (operation.isCancelled) {if (doneBlock) {doneBlock(nil, nil, SDImageCacheTypeNone);}return;}@autoreleasepool {NSData *diskData = [self diskImageDataBySearchingAllPathsForKey:key];UIImage *diskImage;if (image) {// 图像来自内存缓存,但需要图像数据// the image is from in-memory cache, but need image datadiskImage = image;} else if (diskData) {//应该缓存到内存BOOL shouldCacheToMomery = YES;if (context[SDWebImageContextStoreCacheType]) {SDImageCacheType cacheType = [context[SDWebImageContextStoreCacheType] integerValue];shouldCacheToMomery = (cacheType == SDImageCacheTypeAll || cacheType == SDImageCacheTypeMemory);}// 解码图像数据,只有在内存缓存错过// decode image data only if in-memory cache misseddiskImage = [self diskImageForKey:key data:diskData options:options context:context];if (shouldCacheToMomery && diskImage && self.config.shouldCacheImagesInMemory) {// cost 被用来计算缓存中所有对象的代价。当内存受限或者所有缓存对象的总代价超过了最大允许的值时,缓存会移除其中的一些对象。NSUInteger cost = diskImage.sd_memoryCost;//存入内存缓存中[self.memoryCache setObject:diskImage forKey:key cost:cost];}}if (doneBlock) {if (shouldQueryDiskSync) {doneBlock(diskImage, diskData, SDImageCacheTypeDisk);} else {dispatch_async(dispatch_get_main_queue(), ^{doneBlock(diskImage, diskData, SDImageCacheTypeDisk);});}}}};// Query in ioQueue to keep IO-safe// 在唯一的子线程:self.ioQueue中查询,保证io安全//应该同步查询磁盘if (shouldQueryDiskSync) {//线程等待dispatch_sync(self.ioQueue, queryDiskBlock);} else {//线程不等待dispatch_async(self.ioQueue, queryDiskBlock);}return operation;
}
SDWebImageDownloader
- (nullable SDWebImageDownloadToken *)downloadImageWithURL:(nullable NSURL *)urloptions:(SDWebImageDownloaderOptions)optionscontext:(nullable SDWebImageContext *)contextprogress:(nullable SDWebImageDownloaderProgressBlock)progressBlockcompleted:(nullable SDWebImageDownloaderCompletedBlock)completedBlock {// The URL will be used as the key to the callbacks dictionary so it cannot be nil. If it is nil immediately call the completed block with no image or data.// URL将被用作回调字典的键,所以它不能为nil。如果为nil,立即调用没有图像或数据的已完成块。if (url == nil) {//如果completedBlock存在但是因为没有url所以就没办法访问,就直接提示错误就行if (completedBlock) {NSError *error = [NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorInvalidURL userInfo:@{NSLocalizedDescriptionKey : @"Image url is nil"}];completedBlock(nil, nil, error, YES);}return nil;}SD_LOCK(self.operationsLock);//定义一个下载操作取消令牌id downloadOperationCancelToken;//当前下载操作中取出SDWebImageDownloaderOperation实例NSOperation<SDWebImageDownloaderOperation> *operation = [self.URLOperations objectForKey:url];// There is a case that the operation may be marked as finished or cancelled, but not been removed from `self.URLOperations`.// 有一种情况是,操作可能被标记为完成或取消,但没有从'self.URLOperations'中删除。//操作不存在||操作已完成||操作被取消if (!operation || operation.isFinished || operation.isCancelled) {//创建一个下载操作给operation//operation中保存了progressBlock和completedBlockoperation = [self createDownloaderOperationWithUrl:url options:options context:context];//如果操作不存在,即上述创建失败,返回一个错误信息if (!operation) {SD_UNLOCK(self.operationsLock);if (completedBlock) {NSError *error = [NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorInvalidDownloadOperation userInfo:@{NSLocalizedDescriptionKey : @"Downloader operation is nil"}];completedBlock(nil, nil, error, YES);}return nil;}@weakify(self);//如果创建成功了,我们再继续完善其completionBlock代码块operation.completionBlock = ^{@strongify(self);if (!self) {return;}//在操作数组中删除此url的操作SD_LOCK(self.operationsLock);[self.URLOperations removeObjectForKey:url];SD_UNLOCK(self.operationsLock);};//将该url的下载操作赋值给self.URLOperations操作数组self.URLOperations[url] = operation;// Add the handlers before submitting to operation queue, avoid the race condition that operation finished before setting handlers.// 在提交到操作队列之前添加处理程序,避免操作在设置处理程序之前完成的竞态条件。//下载操作取消令牌downloadOperationCancelToken = [operation addHandlersForProgress:progressBlock completed:completedBlock];// Add operation to operation queue only after all configuration done according to Apple's doc.// `addOperation:` does not synchronously execute the `operation.completionBlock` so this will not cause deadlock.// 根据苹果文档完成所有配置后,才能将操作添加到操作队列中。// 'addOperation:'不会同步执行'operation.completionBlock',因此不会导致死锁。//将此下载操作添加到下载队列[self.downloadQueue addOperation:operation];} else {// When we reuse the download operation to attach more callbacks, there may be thread safe issue because the getter of callbacks may in another queue (decoding queue or delegate queue)// So we lock the operation here, and in `SDWebImageDownloaderOperation`, we use `@synchonzied (self)`, to ensure the thread safe between these two classes.// 当我们重用下载操作来附加更多的回调时,可能会有线程安全问题,因为回调的getter可能在另一个队列(解码队列或委托队列)// 所以我们锁定了这里的操作,在'SDWebImageDownloaderOperation'中,我们使用' @synchronized(self)'来确保这两个类之间的线程安全。@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);//创建该下载的token,这里 downloadOperationCancelToken 默认是一个字典,存放 progressBlock 和 completedBlock//使用operation初始化该下载操作tokenSDWebImageDownloadToken *token = [[SDWebImageDownloadToken alloc] initWithDownloadOperation:operation];token.url = url; //保存urltoken.request = operation.request; //保存请求token.downloadOperationCancelToken = downloadOperationCancelToken; //保存下载操作取消令牌return token;
}
我们发现这个方法在之前的方法里并没有调用过,我们在全局搜索下这个函数在哪用过呢?
在这个函数里:
这个方法是一个工具,用来生成SDWebImageDownloaderOptions对象,我们向前找发现在SDWebImageManager的callDownloadProcessForOperation方法里调了这个:
- (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 refreshingdownloaderOptions &= ~SDWebImageDownloaderProgressiveLoad;// ignore image read from NSURLCache if image if cached but force refreshingdownloaderOptions |= SDWebImageDownloaderIgnoreCachedResponse;}return [self downloadImageWithURL:url options:downloaderOptions context:context progress:progressBlock completed:completedBlock];
}
然后我们再看downloadImageWithURL里用到的的方法,这个才是真正的下载:
通过这个函数createDownloaderOperationWithUrl赋值给了operation然后通过operation才赋值给了token
- (nullable NSOperation<SDWebImageDownloaderOperation> *)createDownloaderOperationWithUrl:(nonnull NSURL *)urloptions:(SDWebImageDownloaderOptions)optionscontext:(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// 为了防止潜在的重复缓存(NSURLCache + SDImageCache),我们禁用图像请求的缓存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 Option// Context选项SDWebImageMutableContext *mutableContext;if (context) {mutableContext = [context mutableCopy];} else {mutableContext = [NSMutableDictionary dictionary];}// Request Modifier//请求修饰符,设置请求修饰符,在图像加载之前修改原始的下载请求。返回nil将取消下载请求。id<SDWebImageDownloaderRequestModifier> requestModifier;if ([context valueForKey:SDWebImageContextDownloadRequestModifier]) {requestModifier = [context valueForKey:SDWebImageContextDownloadRequestModifier];} else {//self.requestModifier默认为nil,表示不修改原始下载请求。requestModifier = self.requestModifier;}NSURLRequest *request;//如果请求修饰符存在if (requestModifier) {NSURLRequest *modifiedRequest = [requestModifier modifiedRequestWithRequest:[mutableRequest copy]];// If modified request is nil, early return// 如果修改请求为nil,则提前返回if (!modifiedRequest) {return nil;} else {request = [modifiedRequest copy];}} else {request = [mutableRequest copy];}// Response Modifier// 响应修饰符,设置响应修饰符来修改图像加载期间的原始下载响应。返回nil将标志当前下载已取消。id<SDWebImageDownloaderResponseModifier> responseModifier;if ([context valueForKey:SDWebImageContextDownloadResponseModifier]) {responseModifier = [context valueForKey:SDWebImageContextDownloadResponseModifier];} else {//self.responseModifier默认为nil,表示不修改原始下载响应。responseModifier = self.responseModifier;}//如果响应修饰存在if (responseModifier) {mutableContext[SDWebImageContextDownloadResponseModifier] = responseModifier;}// Decryptor// 图像解码,设置解密器对原始下载数据进行解密后再进行图像解码。返回nil将标志下载失败。id<SDWebImageDownloaderDecryptor> decryptor;if ([context valueForKey:SDWebImageContextDownloadDecryptor]) {decryptor = [context valueForKey:SDWebImageContextDownloadDecryptor];} else {//self.decryptor默认为nil,表示不修改原始下载数据。decryptor = self.decryptor;}//如果图像解码操作存在if (decryptor) {mutableContext[SDWebImageContextDownloadDecryptor] = decryptor;}context = [mutableContext copy];// Operation Class// 操作类Class operationClass = self.config.operationClass;//操作类存在 && 操作类是NSOperation的实例类 && 操作类遵守SDWebImageDownloaderOperation协议if (operationClass && [operationClass isSubclassOfClass:[NSOperation class]] && [operationClass conformsToProtocol:@protocol(SDWebImageDownloaderOperation)]) {// Custom operation class// 自定义操作类(可以自行修改和定义)} else {//默认操作类operationClass = [SDWebImageDownloaderOperation class];}//创建下载操作:SDWebImageDownloaderOperation用于请求网络资源的操作,它是一个 NSOperation 的子类NSOperation<SDWebImageDownloaderOperation> *operation = [[operationClass alloc] initWithRequest:request inSession:self.session options:options context:context];//如果operation实现了setCredential:方法if ([operation respondsToSelector:@selector(setCredential:)]) {//url证书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];}}//如果operation实现了setMinimumProgressInterval:方法if ([operation respondsToSelector:@selector(setMinimumProgressInterval:)]) {operation.minimumProgressInterval = MIN(MAX(self.config.minimumProgressInterval, 0), 1);}//设置该url的操作优先级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// 通过系统地模拟后进先出的执行顺序,前一个添加的操作可以依赖于新操作// 这样可以保证先执行新操作,即使有些操作完成了,同时又追加了新操作// 仅仅使上次添加的操作依赖于新的操作并不能解决这个问题。参见测试用例#test15DownloaderLIFOExecutionOrderfor (NSOperation *pendingOperation in self.downloadQueue.operations) {[pendingOperation addDependency:operation];}}return operation;
}
SDWebImage总共嵌套了七八层类去完成操作,比之前学的JSONModel复杂很多,我看得还比较浅,在这补充下一些类的作用:
- SDWebImageDownloaderOperation: 下载器的具体实现,继承自NSOperation,使用NSURLSession来执行具体的下载操作。
SDWebImageDownloaderConfig: 下载器的配置类,用于配置下载选项,例如是否使用NSURLCache,是否允许蜂窝网络下载等。 - SDWebImageManager: 负责图片下载和缓存的管理。
SDImageCache: 图片缓存器,用于缓存已下载的图片数据,避免重复下载。
SDImageCacheConfig: 缓存器的配置类,用于配置缓存选项,例如缓存路径、缓存时长等。 - SDWebImageOperation: 图片加载器的具体实现,继承自NSOperation,用于加载和显示图片。
- SDAnimatedImage: 用于播放动态图片。
- SDAnimatedImageView: 用于显示动态图片的UIImageView子类。
- SDWebImageCompat: 用于提供一些跨平台的API,例如判断图片类型、生成缩略图等。
- UIImage+MultiFormat: 用于支持多种图片格式,例如WebP、GIF等。
- SDWebImageDefine: 定义了一些宏,例如SD_LOCK、SD_UNLOCK等。
- SDWebImageError: 定义了一些错误码和错误描述。
- SDWebImagePrefetcher: 用于预下载图片数据,避免在需要显示图片时还需要进行下载操作。