【iOS】——SDWebImage源码学习

文章目录

  • 一、SDWebIamge简介
  • 二、SDWebImage的调用流程
  • SDWebImage源码分析
    • 1.UIImageView+WebCache层
    • 2.UIView+WebCache层
    • 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];方法时,会执行UIImageView+WebCache类中的相应方法,当使用[button sd_setImageWithURL:imageUrl forState:UIControlStateNormal placeholderImage:nil];方法时会执行UIBUtton+WebCache类中的相应方法,但是最后都会调用UIView+WebCache类中的- (nullable id<SDWebImageOperation>)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.UIImageView+WebCache层

- (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);}}];
}

不难发现上面的方法最后都会调用到下面这个方法,也就是基类方法。

//UIImageView+WebCache
- (void)sd_setImageWithURL:(nullable NSURL *)urlplaceholderImage:(nullable UIImage *)placeholderoptions:(SDWebImageOptions)optionsprogress:(nullable SDWebImageDownloaderProgressBlock)progressBlockcompleted:(nullable SDExternalCompletionBlock)completedBlock;

2.UIView+WebCache层

接着在上面的方法中又会调用到UIView+WebCache层的下面这个方法

/*
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是否在失败列表中(已标记为失败的URL)BOOL 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 {// 获取要使用的图像缓存实例id<SDImageCache> 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属性id<SDImageLoader> 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;// 根据上下文中的缓存键过滤器生成缓存键(用于唯一标识图片资源)id<SDWebImageCacheKeyFilter> 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关联的下载操作(如果存在)NSOperation<SDWebImageDownloaderOperation> *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。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/624624.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

寄快递怕被骗?快来使用闪侠惠递寄快递避免这种麻烦!

我们去快递驿站寄快递的时候&#xff0c;总是快递的工作人员说是多少钱就要花费多少钱&#xff0c;这样不免会出现快递费高昂的局面&#xff0c;万一工作人员谎报了呢。那岂不是我们就要多花钱了呢&#xff0c;所以说究竟有没有一款寄快递的“神器”来便宜寄快递呢&#xff1f;…

有没有批量调节音乐播放速度的方法?调节音频的播放速度

一&#xff0c;引言​ 调节音乐播放的速度是一种有趣而富有创造性的方式&#xff0c;可以改变我们对音乐的感知和体验。通过调整音乐的速度&#xff0c;我们可以创造出全新的听觉效果&#xff0c;让音乐呈现出不同的情感和氛围。二&#xff0c;调节音乐速度的效果 调节音乐速…

《中医临床诊疗术语》数据库

最新版的《中医临床诊疗术语》于2023年3月17日由国家中医药管理局提出的&#xff0c;由国家市场监督管理总局和国家标准化管理委员会共同发布。新版的修订是为落实相关政策文件要求&#xff0c;推进中医医疗服务规范化、标准化管理&#xff0c;提高中医医疗服务标准化水平和管理…

【JavaScript】Promise 语法

前人栽树 许大仙 aexiar.github.io 前言 函数对象和实例对象 函数对象&#xff1a;将函数作为对象使用&#xff0c;简称为函数对象。实例对象&#xff1a;new 构造函数或类产生的对象&#xff0c;称为实例对象。 示例&#xff1a; <template> </template><s…

OpenHarmony实战开发-如何实现发布图片评论功能。

介绍 本示例将通过发布图片评论场景&#xff0c;介绍如何使用startAbilityForResult接口拉起相机拍照&#xff0c;并获取相机返回的数据。 效果图预览 使用说明 通过startAbilityForResult接口拉起相机&#xff0c;拍照后获取图片地址。 实现思路 1.创建CommentData类&…

【LatentDiffusion 代码详解(1)】LatentDiffusion 的 yaml 解读

YAML 文件提供了一种清晰、简洁且易于理解的方式来描述配置信息&#xff0c;特别适用于机器学习模型的超参数调优和实验管理。 以 Latent Diffusion 官方代码仓库中的 https://github.com/CompVis/latent-diffusion/blob/main/configs/autoencoder/autoencoder_kl_32x32x4.yam…

了解 Unity AI:从初学者到高级的综合指南

游戏中的AI是什么? 游戏中的人工智能是指利用人工智能技术使视频游戏中的非玩家角色和实体智能地行动、做出决策、对游戏环境做出反应,并提供引人入胜的动态游戏体验。什么是NPC? NPC 代表“非玩家角色”。NPC 是视频游戏、角色扮演游戏中不受人类玩家控制的角色。它们是计算…

Swoole 实践篇之结合 WebRTC 实现音视频实时通信方案

原文首发链接&#xff1a;Swoole 实践篇之结合 WebRTC 实现音视频实时通信方案 大家好&#xff0c;我是码农先森。 引言 这次实现音视频实时通信的方案是基于 WebRTC 技术的&#xff0c;它是一种点对点的通信技术&#xff0c;通过浏览器之间建立对等连接&#xff0c;实现音频…

java线程(1)

1、多线程启动 有两种启动方式 1、实现Runable接口 2、继承Thread类并且重写run&#xff08;&#xff09;方法 在执行进程中的任务才会产生线程&#xff0c;所以需要实现Runable接口并且重写run&#xff08;&#xff09;方法&#xff0c;然后将Runable的实现对象作为参数传…

官宣:2024第二十届国际铸造件展12月精彩呈现!

Shanghai International Die-casting Casting Expo 2024第二十届上海国际压铸、铸造展览会 2024第二十届上海国际压铸、铸件产品展 时间&#xff1a;2024年12月18-20日 地点&#xff1a;上海新国际博览中心&#xff08;浦东区龙阳路2345号&#xff09; 报名参展&#xff1…

Delphi Xe 10.3 钉钉SDK开发——审批流接口(获取表单ProcessCode)

开发钉钉审批流时&#xff0c;需要用到钉钉表单的Processcode&#xff0c;有两种方法 &#xff1a; 一、手动获取&#xff1a; 管理员后台——审批——找到对应的表单&#xff1a;如图&#xff1a; ProcessCode后面就是了&#xff01; 二、接口获取&#xff1a;今天的重点&a…

conda新建环境报错An HTTP error occurred when trying to retrieve this URL.

conda新建环境报错如下 cat .condarc #将 .condarc文件中的内容删除&#xff0c;改成下面的内容 vi .condarc channels:- defaults show_channel_urls: true default_channels:- https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/main- https://mirrors.tuna.tsinghua.…