做网站常用工具,网页设计与制作读书心得体会1000字,经典营销案例分析,创建地址怎么弄文章目录 一、SDWebIamge简介二、SDWebImage的调用流程SDWebImage源码分析1.UIImageViewWebCache层2.UIViewWebCache层3.SDWebManager层4.SDWebCache层5.SDWebImageDownloader层 一、SDWebIamge简介
SDWebImage是iOS中提供图片加载的第三方库#xff0c;可以给UIKit框架中的控… 文章目录 一、SDWebIamge简介二、SDWebImage的调用流程SDWebImage源码分析1.UIImageViewWebCache层2.UIViewWebCache层3.SDWebManager层4.SDWebCache层5.SDWebImageDownloader层 一、SDWebIamge简介
SDWebImage是iOS中提供图片加载的第三方库可以给UIKit框架中的控件比如UIImageView和UIButton提供从网络上下载和缓存的图片。它的接口十分简洁如果给UIImageView控件添加图片可以使用如下代码
[imageView sd_setImageWithURL:imageUrl placeholderImage:nil];//第一个参数是图片的URL第二个参数是占位图片加载失败时显示如果给UIButton添加图片可以使用如下代码
[button sd_setImageWithURL:imageUrl forState:UIControlStateNormal placeholderImage:nil];//第一个参数是图片的URL第二个参数是按钮状态第三个参数是占位图片加载失败时显示SDWebImage有下面一些常见的功能
通过异步方式加载图片可以自动缓存到内存和磁盘中并且可以自动清理过期的缓存支持多种的图片格式包括jpg、jepg、png等同时还支持多种动图格式包括GIF、APNG等同一图片的URL不会重复下载对失效的图片URL不会重复尝试下载在子线程中进行操作确保不会阻塞主线程
二、SDWebImage的调用流程 当使用[imageView sd_setImageWithURL:imageUrl placeholderImage:nil];方法时会执行UIImageViewWebCache类中的相应方法当使用[button sd_setImageWithURL:imageUrl forState:UIControlStateNormal placeholderImage:nil];方法时会执行UIBUttonWebCache类中的相应方法但是最后都会调用UIViewWebCache类中的- (nullable idSDWebImageOperation)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 {};方法。接着根据URL通过SDWebImageManager的loadImageWithURL:options:context:progress:completed:方法加载图片接着通过sd_setImageLoadOperation方法将operation加入到SDOperationsDictionary中。然后调用queryCacheOperationForKey方法进行查询图片缓存通过查询内存和磁盘中是否有缓存如果有则通过回调函数显示照片如果没有则调用downloadImageWithURL:options:context:progress: completed:方法进行图片下载和缓存最后显示图片。
SDWebImage源码分析
1.UIImageViewWebCache层
- (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 placeholderImage:(nullable UIImage *)placeholder options:(SDWebImageOptions)options context:(nullable SDWebImageContext *)context {[self sd_setImageWithURL:url placeholderImage:placeholder options:options context:context 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];
}- (void)sd_setImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder options:(SDWebImageOptions)options progress:(nullable SDImageLoaderProgressBlock)progressBlock completed:(nullable SDExternalCompletionBlock)completedBlock {[self sd_setImageWithURL:url placeholderImage:placeholder options:options context:nil progress:progressBlock completed:completedBlock];
}- (void)sd_setImageWithURL:(nullable NSURL *)urlplaceholderImage:(nullable UIImage *)placeholderoptions:(SDWebImageOptions)optionscontext:(nullable SDWebImageContext *)contextprogress:(nullable SDImageLoaderProgressBlock)progressBlockcompleted:(nullable SDExternalCompletionBlock)completedBlock {[self sd_internalSetImageWithURL:urlplaceholderImage:placeholderoptions:optionscontext:contextsetImageBlock:nilprogress:progressBlockcompleted:^(UIImage * _Nullable image, NSData * _Nullable data, NSError * _Nullable error, SDImageCacheType cacheType, BOOL finished, NSURL * _Nullable imageURL) {if (completedBlock) {completedBlock(image, error, cacheType, imageURL);}}];
}不难发现上面的方法最后都会调用到下面这个方法也就是基类方法。
//UIImageViewWebCache
- (void)sd_setImageWithURL:(nullable NSURL *)urlplaceholderImage:(nullable UIImage *)placeholderoptions:(SDWebImageOptions)optionsprogress:(nullable SDWebImageDownloaderProgressBlock)progressBlockcompleted:(nullable SDExternalCompletionBlock)completedBlock
2.UIViewWebCache层
接着在上面的方法中又会调用到UIViewWebCache层的下面这个方法
/*
imageURL: NSURL 类型指定了要加载图片的远程URL。这是图片请求的核心依据。
options: SDWebImageOptions 枚举类型包含了多个可选标志位用于控制图片加载的行为如是否只从内存缓存加载、是否同步查询缓存、是否允许重定向、是否使用渐进式加载等。
progressBlock: SDWebImageDownloaderProgressBlock 类型一个进度回调块当图片下载过程中更新进度时会被调用传递已下载数据量和总数据量。
completedBlock: SDWebImageCompletionBlock 类型一个完成回调块当图片加载成功、失败或被取消时会被调用。它接收以下参数
image: 加载成功的UIImage对象或在加载失败时为nil。
data: 图片对应的原始NSData对象可能用于进一步处理或存储。
error: 如果加载失败包含错误信息的NSError对象否则为nil。
cacheType: 表示图片来源于哪种缓存类型的枚举值内存、磁盘或无缓存。
finished: 标记此次加载是否真正完成即使加载失败也可能因为有占位图而标记为YES。
imageURL: 当前请求的URL与函数参数中的imageURL相同提供上下文信息。
*/
- (void)sd_internalSetImageWithURL:(nullable NSURL *)urlplaceholderImage:(nullable UIImage *)placeholderoptions:(SDWebImageOptions)optionsoperationKey:(nullable NSString *)operationKeysetImageBlock:(nullable SDSetImageBlock)setImageBlockprogress:(nullable SDWebImageDownloaderProgressBlock)progressBlockcompleted:(nullable SDExternalCompletionBlock)completedBlock;
上面这个函数主要目的是为控件如UIImageView、UIButton设置图片处理从指定URL加载图片的相关逻辑包括异步下载、缓存检查、下载操作、过渡动画应用以及完成回调的触发。 函数里有个[self sd_cancelImageLoadOperationWithKey:validOperationKey];方法是根据key取消当前操作针对于比如cell中的UIImageView被复用的时候首先需要根据key取消当前imageView上的下载或者缓存操作 3.SDWebManager层
在上面的方法中又会调用到SDWebmanager层的loadImageWithURL: options: context: progress: completed:方法其具体实现细节如下
/*** 加载指定URL的图像支持多种选项、上下文以及进度和完成回调。** param url 图像URL可为nil或无效。如果传入的是NSString类型会自动转换为NSURL。若非NSURL类型则置为nil。* param options 加载选项如缓存策略、重试失败图片等。* param context 上下文信息字典包含如回调队列、下载器、解码器等自定义设置。* param progressBlock 图像加载进度回调返回已加载的数据长度和总长度。* param completedBlock 图像加载完成回调返回加载结果成功或失败、图像、数据、缓存类型、URL等信息。* return 返回一个SDWebImageCombinedOperation对象可用于取消或查询加载状态。*/
- (SDWebImageCombinedOperation *)loadImageWithURL:(nullable NSURL *)urloptions:(SDWebImageOptions)optionscontext:(nullable SDWebImageContext *)contextprogress:(nullable SDImageLoaderProgressBlock)progressBlockcompleted:(nonnull SDInternalCompletionBlock)completedBlock {// 确保已完成回调块不为空否则调用此方法无意义NSAssert(completedBlock ! nil, 若要预取图像请使用-[SDWebImagePrefetcher prefetchURLs]方法);// 处理URL类型允许传入NSString并转换为NSURLif ([url isKindOfClass:NSString.class]) {url [NSURL URLWithString:(NSString *)url];}// 防止传入非NSURL类型的无效URL如NSNull将其置为nilif (![url isKindOfClass:NSURL.class]) {url nil;}// 创建一个新的图像加载操作对象SDWebImageCombinedOperation *operation [SDWebImageCombinedOperation new];operation.manager self;// 检查URL是否在失败列表中已标记为失败的URLBOOL isFailedUrl NO;if (url) {SD_LOCK(_failedURLsLock);isFailedUrl [self.failedURLs containsObject:url];SD_UNLOCK(_failedURLsLock);}// 根据URL、选项和上下文预处理并生成最终加载结果SDWebImageOptionsResult *result [self processedResultForURL:url options:options context:context];// 若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;[self callCompletionBlockForOperation:operation completion:completedBlock error:[NSError errorWithDomain:SDWebImageErrorDomain code:code userInfo:{NSLocalizedDescriptionKey : description}] queue:result.context[SDWebImageContextCallbackQueue] url:url];return operation;}// 将当前操作添加到正在运行的操作列表中SD_LOCK(_runningOperationsLock);[self.runningOperations addObject:operation];SD_UNLOCK(_runningOperationsLock);// 开始从缓存加载图像后续步骤如下// 1. 查询缓存中的图像可能涉及原始图像和经过变换的图像取决于是否有变换器// 2. 缓存未命中时下载数据和图像// 3. 存储图像到缓存可能同时存储原始图像和经过变换的图像// 4. 对图像进行CPU变换若有变换器[self callCacheProcessForOperation:operation url:url options:result.options context:result.context progress:progressBlock completed:completedBlock];return operation;
}上面方法的主要功能就是异步加载指定URL的图像资源 1.首先确保completedBlock不为空以便在加载完成后执行回调。同时对传入的url参数进行类型检查和转换确保其为有效的NSURL对象。 2.接着实例化一个SDWebImageCombinedOperation对象用于管理整个图片加载过程并将其与当前SDWebImageManager实例关联。 3.如果URL有效检查其是否在失败URL列表中即之前加载该URL时失败且未被重试。这一步有助于避免重复尝试已知失败的请求。根据URL、选项和上下文生成一个SDWebImageOptionsResult对象其中包含了实际应用的加载选项和处理后的上下文信息。 4.如果URL为空、无效或者已标记为失败且未开启SDWebImageRetryFailed选项立即调用完成回调报告错误并返回操作对象。将当前加载操作加入到正在运行的操作列表中便于全局管理所有正在进行的加载任务。 5.最后调用callCacheProcessForOperation方法开始从缓存查找图像如果缓存未命中则启动网络下载并在下载完成后存储图像到缓存。在整个过程中可能会根据上下文中的变换器对图像进行CPU处理。同时如果提供了progressBlock会在加载过程中定期回调更新加载进度。
上面代码最后调用到callCacheProcessForOperation方法这个方法的具体实现如下
- (void)callCacheProcessForOperation:(nonnull SDWebImageCombinedOperation *)operationurl:(nonnull NSURL *)urloptions:(SDWebImageOptions)optionscontext:(nullable SDWebImageContext *)contextprogress:(nullable SDImageLoaderProgressBlock)progressBlockcompleted:(nullable SDInternalCompletionBlock)completedBlock {// 获取要使用的图像缓存实例idSDImageCache imageCache context[SDWebImageContextImageCache];if (!imageCache) {imageCache self.imageCache; // 如果上下文中没有指定缓存则使用默认缓存}// 获取查询缓存类型SDImageCacheType queryCacheType SDImageCacheTypeAll; // 默认查询所有缓存类型if (context[SDWebImageContextQueryCacheType]) {queryCacheType [context[SDWebImageContextQueryCacheType] integerValue]; // 如果上下文中指定了查询缓存类型则使用指定值}// 判断是否需要查询缓存根据options判断BOOL shouldQueryCache !SD_OPTIONS_CONTAINS(options, SDWebImageFromLoaderOnly); // 如果选项中不包含仅从加载器加载则需要查询缓存if (shouldQueryCache) {// 计算转换后的缓存键NSString *key [self cacheKeyForURL:url context:context];// 为避免循环引用对operation进行弱引用weakify(operation);// 向缓存发起查询请求operation.cacheOperation [imageCache queryImageForKey:key options:options context:context cacheType:queryCacheType completion:^(UIImage * _Nullable cachedImage, NSData * _Nullable cachedData, SDImageCacheType cacheType) {// 恢复对operation的强引用strongify(operation);// 检查操作是否已被取消或不存在if (!operation || operation.isCancelled) {// 用户取消了操作调用完成回调并移除运行中的操作[self callCompletionBlockForOperation:operation completion:completedBlock error:[NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorCancelled userInfo:{NSLocalizedDescriptionKey : Operation cancelled by user during querying the cache}] queue:context[SDWebImageContextCallbackQueue] url:url];[self safelyRemoveOperationFromRunning:operation];return;} else if (!cachedImage) {// 缓存中未找到图像// 获取原始缓存键NSString *originKey [self originalCacheKeyForURL:url context:context];// 判断是否有可能在原始缓存中找到图像未经过转换BOOL mayInOriginalCache ![key isEqualToString:originKey];if (mayInOriginalCache) {// 有可能在原始缓存中找到图像尝试查询原始缓存[self callOriginalCacheProcessForOperation:operation url:url options:options context:context progress:progressBlock completed:completedBlock];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];}
}
这里主要是判断任务是否该走缓存查询或者直接下载。如果是缓存查询就进入SDImageCache里面进行缓存查询且在此处理缓存结果的回调。否则就调用callDownloadProcessForOperation进入下一步判断。 继续往下说loadImageWithURL中的工作流程下面一个重要的方法就是queryCacheOperationForKey(),在SDImageCache里查询是否存在缓存的图片
4.SDWebCache层
queryCacheOperationForKey()方法的实现细节如下
param key 缓存键值用于标识特定的图片资源。如果为nil则直接返回nil并调用完成回调。* param options 查询选项如是否仅解码第一帧、是否检查动画图片类匹配等。* param context 上下文信息包含如回调队列、期望的动画图片类、存储缓存类型等。* param queryCacheType 待查询的缓存类型内存、磁盘或两者皆查。* param doneBlock 完成回调传递查询结果图片、数据、缓存类型。** return SDImageCacheToken对象表示正在进行的查询操作。可用于取消查询。*/
-(nullable SDImageCacheToken *)queryCacheOperationForKey:(nullable NSString *)keyoptions:(SDImageCacheOptions)optionscontext:(nullable SDWebImageContext *)contextcacheType:(SDImageCacheType)queryCacheTypedone:(nullable SDImageCacheQueryCompletionBlock)doneBlock {// 如果键值为空则立即返回nil并调用完成回调if (!key) {if (doneBlock) {doneBlock(nil, nil, SDImageCacheTypeNone);}return nil;}// 非法缓存类型直接返回nil并调用完成回调if (queryCacheType SDImageCacheTypeNone) {if (doneBlock) {doneBlock(nil, nil, SDImageCacheTypeNone);}return nil;}// 首先检查内存缓存...UIImage *image;if (queryCacheType ! SDImageCacheTypeDisk) {image [self imageFromMemoryCacheForKey:key]; // 获取内存缓存中的图片}// 若内存缓存命中则根据选项进一步处理图片if (image) {if (options SDImageCacheDecodeFirstFrameOnly) { // 仅解码第一帧选项// 确保静态图片即非动画图片if (image.sd_imageFrameCount 1) {#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) { // 检查动画图片类匹配选项// 根据上下文中的期望动画图片类进行检查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);}return nil;}// 初始化查询操作令牌并设置相关属性SDCallbackQueue *queue context[SDWebImageContextCallbackQueue];SDImageCacheToken *operation [[SDImageCacheToken alloc] initWithDoneBlock:doneBlock];operation.key key;operation.callbackQueue queue;// 判断是否需要同步查询磁盘缓存// 1. 内存缓存命中且要求同步查询内存数据// 2. 内存缓存未命中且要求同步查询磁盘数据BOOL shouldQueryDiskSync ((image options SDImageCacheQueryMemoryDataSync) ||(!image options SDImageCacheQueryDiskDataSync));// 定义磁盘数据查询闭包NSData* (^queryDiskDataBlock)(void) ^NSData* {synchronized (operation) {if (operation.isCancelled) {return nil;}}return [self diskImageDataBySearchingAllPathsForKey:key]; // 从磁盘缓存中获取图片数据};// 定义磁盘图片解析闭包根据磁盘数据生成UIImage对象UIImage* (^queryDiskImageBlock)(NSData*) ^UIImage*(NSData* diskData) {synchronized (operation) {if (operation.isCancelled) {return nil;}}UIImage *diskImage;if (image) { // 图片已从内存缓存获取仅需根据数据生成UIImagediskImage image;} else if (diskData) { // 从磁盘缓存获取到数据需解析为UIImageBOOL shouldCacheToMomery YES;if (context[SDWebImageContextStoreCacheType]) {SDImageCacheType cacheType [context[SDWebImageContextStoreCacheType] integerValue];shouldCacheToMomery (cacheType SDImageCacheTypeAll || cacheType SDImageCacheTypeMemory);}CGSize thumbnailSize CGSizeZero;NSValue *thumbnailSizeValue context[SDWebImageContextImageThumbnailPixelSize];if (thumbnailSizeValue ! nil) {#if SD_MACthumbnailSize thumbnailSizeValue.sizeValue;#elsethumbnailSize thumbnailSizeValue.CGSizeValue;#endif}if (thumbnailSize.width 0 thumbnailSize.height 0) {// 查询生成缩略图的全尺寸缓存键时不应将缩略图写回全尺寸内存缓存shouldCacheToMomery NO;}// 特殊情况当用户针对同一URL在列表中查询图片时为了避免多次解码和写入相同图像对象到磁盘缓存这里再次检查内存缓存if (shouldCacheToMomery self.config.shouldCacheImagesInMemory) {diskImage [self.memoryCache objectForKey:key];}// 如果内存缓存未命中才进行解码if (!diskImage) {diskImage [self diskImageForKey:key data:diskData options:options context:context];if (shouldCacheToMomery diskImage self.config.shouldCacheImagesInMemory) {NSUInteger cost diskImage.sd_memoryCost;[self.memoryCache setObject:diskImage forKey:key cost:cost]; // 将解析后的图片写入内存缓存}}}return diskImage;};// 根据是否同步查询磁盘缓存执行相应操作if (shouldQueryDiskSync) {__block NSData* diskData;__block UIImage* diskImage;dispatch_sync(self.ioQueue, ^{diskData queryDiskDataBlock();diskImage queryDiskImageBlock(diskData);});// 同步查询时直接在当前线程调用完成回调if (doneBlock) {doneBlock(diskImage, diskData, SDImageCacheTypeDisk);}} else {dispatch_async(self.ioQueue, ^{NSData* diskData queryDiskDataBlock();UIImage* diskImage queryDiskImageBlock(diskData);synchronized (operation) {if (operation.isCancelled) {return;}}// 异步查询时在指定回调队列或主线程异步调用完成回调if (doneBlock) {[(queue ?: SDCallbackQueue.mainQueue) async:^{// 在从IO队列切换至主线程的过程中可能被取消因此在此处再次检查是否已取消synchronized (operation) {if (operation.isCancelled) {return;}}doneBlock(diskImage, diskData, SDImageCacheTypeDisk);}];}});}return operation;
}上面的代码很长但总结下来就是做了三件事 1.先检查键值是否为空并且图片类型是否合法如果不为空并且合法的情况再执行下面的操作否则直接执行回调 2.在内存中查找缓存如果仅查询内存缓存或已从内存缓存获取到图片且不需要查询内存数据直接执行回调函数如果没有查到的话接着执行下面的操作 3.在磁盘中查找缓存这里分两种情况第一种是在内存中已经查到缓存但是还接着要在磁盘中继续查找第二种是在内存中没有查到缓存在磁盘中尝试寻找。如果在磁盘中找到缓存的话并且内存中也有缓存的话则直接解析图片如果在内存中没有的话则将数据传给内存再解析图片。如果磁盘中也没找到缓存的话则先回调再根据operation的属性值决定是否执行下载任务如果执行下载操作的话则调用SDWebManager层的callDownloadProcessForOperation:方法进行下载前的一些配置其实现细节如下
// 定义一个方法用于调用图片下载过程。参数包括当前的图片组合操作SDWebImageCombinedOperation、图片URL、加载选项、上下文信息、已缓存的图片、已缓存的数据、缓存类型、进度回调和完成回调。
- (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 {// 标记缓存操作结束synchronized (operation) {operation.cacheOperation nil; // 清空当前操作的缓存操作引用}// 获取图片加载器优先使用上下文提供的否则使用默认的imageLoader属性idSDImageLoader imageLoader context[SDWebImageContextImageLoader];if (!imageLoader) {imageLoader self.imageLoader;}// 判断是否应该从网络下载图片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 ([imageLoader respondsToSelector:selector(canRequestImageForURL:options:context:)] ? [imageLoader canRequestImageForURL:url options:options context:context] : [imageLoader canRequestImageForURL:url]); // 图片加载器支持该请求if (shouldDownload) { // 需要下载图片的情况if (cachedImage options SDWebImageRefreshCached) { // 缓存存在且要求刷新缓存// 通知已找到缓存图片并尝试重新下载以更新缓存[self callCompletionBlockForOperation:operation completion:completedBlock image:cachedImage data:cachedData error:nil cacheType:cacheType finished:YES queue:context[SDWebImageContextCallbackQueue] url:url];// 将缓存图片传递给图片加载器以便比较远程图片是否与缓存图片一致SDWebImageMutableContext *mutableContext;if (context) {mutableContext [context mutableCopy];} else {mutableContext [NSMutableDictionary dictionary];}mutableContext[SDWebImageContextLoaderCachedImage] cachedImage;context [mutableContext copy]; // 更新上下文}// 弱引用operation防止循环引用weakify(operation);// 发起图片加载请求传入URL、选项、上下文、进度回调和完成回调operation.loaderOperation [imageLoader requestImageWithURL:url options:options context:context progress:progressBlock completed:^(UIImage *downloadedImage, NSData *downloadedData, NSError *error, BOOL finished) {strongify(operation); // 强引用恢复operationif (!operation || operation.isCancelled) { // 操作已被用户取消// 调用完成回调报告操作取消[self callCompletionBlockForOperation:operation completion:completedBlock error:[NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorCancelled userInfo:{NSLocalizedDescriptionKey : Operation cancelled by user during sending the request}] queue:context[SDWebImageContextCallbackQueue] url:url];} else if (cachedImage options SDWebImageRefreshCached [error.domain isEqualToString:SDWebImageErrorDomain] error.code SDWebImageErrorCacheNotModified) { // 图片刷新命中NSURLCache无需调用完成回调// Do nothing} else if ([error.domain isEqualToString:SDWebImageErrorDomain] error.code SDWebImageErrorCancelled) { // 下载操作被用户取消// 调用完成回调报告操作取消[self callCompletionBlockForOperation:operation completion:completedBlock error:error queue:context[SDWebImageContextCallbackQueue] url:url];} else if (error) { // 下载过程中出现错误// 调用完成回调报告错误[self callCompletionBlockForOperation:operation completion:completedBlock error:error queue:context[SDWebImageContextCallbackQueue] url:url];// 根据条件决定是否将失败的URL加入黑名单BOOL shouldBlockFailedURL [self shouldBlockFailedURLWithURL:url error:error options:options context:context];if (shouldBlockFailedURL) {SD_LOCK(self-_failedURLsLock);[self.failedURLs addObject:url];SD_UNLOCK(self-_failedURLsLock);}} else { // 图片下载成功// 如果允许重试失败移除失败URLif (options SDWebImageRetryFailed) {SD_LOCK(self-_failedURLsLock);[self.failedURLs removeObject:url];SD_UNLOCK(self-_failedURLsLock);}// 调用图片转换过程处理下载成功的图片[self callTransformProcessForOperation:operation url:url options:options context:context originalImage:downloadedImage originalData:downloadedData cacheType:SDImageCacheTypeNone finished:finished 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 queue:context[SDWebImageContextCallbackQueue] url:url];// 从运行中的操作列表中移除当前操作[self safelyRemoveOperationFromRunning:operation];} else { // 未找到缓存图片且不允许下载的情况// 调用完成回调报告未找到图片[self callCompletionBlockForOperation:operation completion:completedBlock image:nil data:nil error:nil cacheType:SDImageCacheTypeNone finished:YES queue:context[SDWebImageContextCallbackQueue] url:url];// 从运行中的操作列表中移除当前操作[self safelyRemoveOperationFromRunning:operation];}
}这个函数首先根据传入的options参数判断是否需要下载图片如果存在缓存图片并且请求要求刷新缓存先通知客户端已找到缓存图片并开始重新下载以更新缓存然后将缓存图片信息添加到上下文中以便图片加载器在下载过程中进行比较。接着发起图片下载请求并将返回值存到operation.loaderOperation中以便进行后续的取消操作。
5.SDWebImageDownloader层
对于下载图片的部分会用到downloadImageWithURL方法其具体实现细节如下
param url 图片资源的URL。作为回调字典的键不能为nil。若为nil则立即调用完成回调并返回nil。* param options 下载选项如重试次数、超时时间、HTTP头处理等。* param context 上下文信息包含如解码选项、缓存键过滤器、代理等。* param progressBlock 下载进度回调传递已下载数据大小和总大小。* param completedBlock 下载完成回调传递图片、数据、错误信息以及是否从缓存加载。** return SDWebImageDownloadToken对象表示正在进行的下载任务。可用于取消下载。*/
- (nullable SDWebImageDownloadToken *)downloadImageWithURL:(nullable NSURL *)urloptions:(SDWebImageDownloaderOptions)optionscontext:(nullable SDWebImageContext *)contextprogress:(nullable SDWebImageDownloaderProgressBlock)progressBlockcompleted:(nullable SDWebImageDownloaderCompletedBlock)completedBlock {// 如果URL为nil立即调用完成回调并返回nilif (url nil) {if (completedBlock) {NSError *error [NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorInvalidURL userInfo:{NSLocalizedDescriptionKey : Image url is nil}];completedBlock(nil, nil, error, YES);}return nil;}// 初始化下载操作取消令牌用于取消关联的下载操作id downloadOperationCancelToken;// 根据上下文中的缓存键过滤器生成缓存键用于唯一标识图片资源idSDWebImageCacheKeyFilter cacheKeyFilter context[SDWebImageContextCacheKeyFilter];NSString *cacheKey;if (cacheKeyFilter) {cacheKey [cacheKeyFilter cacheKeyForURL:url];} else {cacheKey url.absoluteString;}// 根据上下文和下载选项生成解码选项SDImageCoderOptions *decodeOptions SDGetDecodeOptionsFromContext(context, [self.class imageOptionsFromDownloaderOptions:options], cacheKey);// 加锁保护操作字典SD_LOCK(_operationsLock);// 从操作字典中获取与URL关联的下载操作如果存在NSOperationSDWebImageDownloaderOperation *operation [self.URLOperations objectForKey:url];// 检查是否可以复用现有下载操作未完成且未取消BOOL shouldNotReuseOperation;if (operation) {synchronized (operation) {shouldNotReuseOperation operation.isFinished || operation.isCancelled;}} else {shouldNotReuseOperation YES;}if (shouldNotReuseOperation) {// 创建新的下载操作operation [self createDownloaderOperationWithUrl:url options:options context:context];if (!operation) {SD_UNLOCK(_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);operation.completionBlock ^{strongify(self);if (!self) {return;}SD_LOCK(self-_operationsLock);[self.URLOperations removeObjectForKey:url];SD_UNLOCK(self-_operationsLock);};// 将新创建的下载操作添加到操作字典[self.URLOperations setObject:operation forKey:url];// 在提交到操作队列之前添加进度和完成回调避免操作完成前回调未设置导致的问题downloadOperationCancelToken [operation addHandlersForProgress:progressBlock completed:completedBlock decodeOptions:decodeOptions];// 将下载操作添加到下载队列[self.downloadQueue addOperation:operation];} else {// 复用已存在的下载操作并附加新的回调synchronized (operation) {downloadOperationCancelToken [operation addHandlersForProgress:progressBlock completed:completedBlock decodeOptions:decodeOptions];}}SD_UNLOCK(_operationsLock);// 创建并初始化下载任务令牌关联下载操作、URL、请求及取消令牌SDWebImageDownloadToken *token [[SDWebImageDownloadToken alloc] initWithDownloadOperation:operation];token.url url;token.request operation.request;token.downloadOperationCancelToken downloadOperationCancelToken;return token;
}downloadImageWithURL方法返回的是一个SDWebImageDownloadToken类型的token这么做的目的是可以在取消的回调中及时取消下载操作。上面代码中的关键是 operation [self createDownloaderOperationWithUrl:url options:options context:context];这行代码中的createDownloaderOperationWithUrl方法执行的是真正执行网络请求的下载操作在执行完成后返回一个operation通过operation进行后面的操作最后返回一个token。