【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.mzph.cn/news/821740.shtml

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

相关文章

C语言【数组】

一、数组基本语法 1. 什么是数组 数组是c语言的一种数据结构&#xff0c;用于存储一组具有相同数据类型的数据&#xff1b; 数组中每个元素可以通过下标进行访问&#xff0c;索引从0开始&#xff0c;最大值为数组长度-1。 2. 数组的使用 类型 数组名[元素个数]; int arr[5]…

ICV:《中美量子产业融资比较分析》

近日&#xff0c;全球前沿科技咨询公司ICV发布了A Comparative Analysis of Quantum Industry Financing in the U.S and China&#xff08;美国和中国量子产业融资比较分析&#xff09;报告。该报告旨在对中美两国在量子技术领域的投融资情况进行比较分析&#xff0c;探讨其差…

基于STC12C5A60S2系列1T 8051单片机的带字库液晶显示器LCD12864数据传输并行模式显示16行点x64列点字模串的应用

基于STC12C5A60S2系列1T 8051单片机的带字库液晶显示器LCD12864数据传输并行模式显示16行点x64列点字模串的应用 STC12C5A60S2系列1T 8051单片机管脚图STC12C5A60S2系列1T 8051单片机I/O口各种不同工作模式及配置STC12C5A60S2系列1T 8051单片机I/O口各种不同工作模式介绍液晶显…

MySQL 试图

视图功能在 5.0 以后的版本启用 视图是一张虚表。数据表确实包含了具体数据并且保存到硬盘中的实表。视图使用数据检索语句动态生 成的一张虚表。每一次数据服务重启或者系统重启之后&#xff0c;在数据库服务启动期间&#xff0c;会使用创建视图的语 句重新生成视图中的数据&…

详解UART通信协议以及FPGA实现

文章目录 一、UART概述二、UART协议帧格式2.1 波特率2.2 奇校验ODD2.3 偶校验EVEN 三、UART接收器设计3.1 接收时序图3.2 Verilog代码3.3 仿真文件测试3.4 仿真结果3.5 上版测试 四、UART发送器设计4.1 发送时序图4.2 Verilog代码4.3 仿真文件测试4.4 仿真结果4.5 上板测试 五、…

用html画一个睡觉的熊动画

<!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><title>睡觉的熊动画</title><link rel"stylesheet" href"./style.css"> </head><body><div id"contain…

免费的 ChatGPT、GPTs、AI绘画(国内版)

&#x1f525;博客主页&#xff1a;白云如幻❤️感谢大家点赞&#x1f44d;收藏⭐评论✍️ ChatGPT3.5、GPT4.0、GPTs、AI绘画相信对大家应该不感到陌生吧&#xff1f;简单来说&#xff0c;GPT-4技术比之前的GPT-3.5相对来说更加智能&#xff0c;会根据用户的要求生成多种内容甚…

袁庭新ES系列14节 | 搭建Elasticsearch集群

前言 单节点的Elasticsearch需要在处理大量数据的时候需要消耗大量内存和CPU资源&#xff0c;数据量大到一定程度就会产生处理瓶颈&#xff0c;甚至会出现宕机。 为了解决单节点ES的处理能力的瓶颈及单节点故障问题&#xff0c;我们考虑使用Elasticsearch集群。接下来袁老师带…

【前后端的那些事】SpringBoot 基于内存的ip访问频率限制切面(RateLimiter)

文章目录 1. 什么是限流2. 常见的限流策略2.1 漏斗算法2.2 令牌桶算法2.3 次数统计 3. 令牌桶代码编写4. 接口测试5. 测试结果 1. 什么是限流 限流就是在用户访问次数庞大时&#xff0c;对系统资源的一种保护手段。高峰期&#xff0c;用户可能对某个接口的访问频率急剧升高&am…

记录一下易语言post get使用WinHttp的操作

最近在学易语言&#xff0c;在进行通讯的时候&#xff0c;出现一些问题&#xff0c;现在记录下来&#xff0c;避免以后继续忘记&#xff0c; 先声明文本型变量jsonPostData jsonPostData &#xff1d; “{hostname:” &#xff0b; hostnameTxt &#xff0b; “,hardcode:” &…

【ARM 裸机】I.MX 启动方式之启动头文件 2

接上一节&#xff1a;【ARM 裸机】I.MX 启动方式之启动头文件 1&#xff1b; 2.3、DCD DCD&#xff0c;Device Configuration Data &#xff0c;就是配置 6ULL 寄存器的&#xff0c;DCD 数据最大限制 1768 字节&#xff1b; CCGR0 是不是很熟悉&#xff1f;对&#xff0c;在…

【静态分析】软件分析课程实验-前置准备

课程&#xff1a;南京大学的《软件分析》课程 平台&#xff1a;Tai-e&#xff08;太阿&#xff09;实验作业平台 1. 实验概述 Tai-e 是一个分析 Java 程序的静态程序分析框架&#xff0c;相比于已有的知名静态程序分析框架&#xff08;如 Soot、Wala 等&#xff09;&#xf…

AI智能体技术突破:引领科技新浪潮

AI智能体技术突破&#xff1a;引领科技新浪潮 基于大模型的 AI Agent 工作流基于大模型的 AI Agent 工作流效果AI Agent 的四种设计模式Reflection 反思设计模式Tool use 工具使用设计模式Planning 规划设计模式Multiagent collaboration 多智能体协作设计模式 吴恩达在红杉美国…

Flink CDC在阿里云DataWorks数据集成应用实践

摘要&#xff1a;本文整理自阿里云 DataWorks 数据集成团队的高级技术专家 王明亚&#xff08;云时&#xff09;老师在 Flink Forward Asia 2023 中数据集成专场的分享。内容主要为以下四部分&#xff1a; 阿里云 DataWorks 数据集成介绍基于 Flink CDC 数据集成新版引擎架构和…

【鸿蒙开发】生命周期

1. UIAbility组件生命周期 UIAbility的生命周期包括Create、Foreground、Background、Destroy四个状态。 UIAbility生命周期状态 1.1 Create状态 Create状态为在应用加载过程中&#xff0c;UIAbility实例创建完成时触发&#xff0c;系统会调用onCreate()回调。可以在该回调中…

会议室预约小程序开源版开发

会议室预约小程序开源版开发 支持设置免费预约和付费预约、积分兑换商城、积分签到等 会议室类目&#xff0c;提供多种类型和设施的会议室选择&#xff0c;满足不同会议需求。 预约日历&#xff0c;展示会议室预约情况&#xff0c;方便用户选择空闲时段。 预约记录&#xff0…

秋招复习笔记——八股文部分:网络基础

TCP/IP 网络模型 应用层 最上层的&#xff0c;也是我们能直接接触到的就是应用层&#xff08;Application Layer&#xff09;&#xff0c;我们电脑或手机使用的应用软件都是在应用层实现。那么&#xff0c;当两个不同设备的应用需要通信的时候&#xff0c;应用就把应用数据传…

java绘图在ubuntu报错

把JRT网站部署到ubuntu桌面系统上&#xff0c;开始没测试绘图部分功能&#xff0c;只试了连PostGreSql部分正常。后面试了生成位图部分发现报错。 报下面错误&#xff1a; (ColorModel.java:220)\n\tat java.desktop/java.awt.image.BufferedImage.(BufferedImage.java:286)\n…

windows Webrtc +VS2019 (M124)下载编译以及调通测试demo

下载depot tools 设置梯子 git config --global http.proxy 127.0.0.1:10000 git config --global https.proxy 127.0.0.1:10000 下载 $ git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git 设置depot_tools目录为环境变量 下载webrtc # 设置系统代…

2024年4月最新版GPT

2024年4月最新版ChatGPT/GPT4, 附上最新的使用教程。 随着人工智能技术的不断发展&#xff0c;ChatGPT和GPT4已经成为了人们日常生活中不可或缺的助手。2024年4月,OpenAI公司推出了最新版本的GPT4,带来了更加强大的功能和更加友好的用户体验。本文将为大家带来最新版GPT4的实用…