【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 *)url
          placeholderImage:(nullable UIImage *)placeholder
                   options:(SDWebImageOptions)options
                   context:(nullable SDWebImageContext *)context
                  progress:(nullable SDImageLoaderProgressBlock)progressBlock
                 completed:(nullable SDExternalCompletionBlock)completedBlock {
    [self sd_internalSetImageWithURL:url
                    placeholderImage:placeholder
                             options:options
                             context:context
                       setImageBlock:nil
                            progress:progressBlock
                           completed:^(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 *)url
          placeholderImage:(nullable UIImage *)placeholder
                   options:(SDWebImageOptions)options
                  progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
                 completed:(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 *)url
                  placeholderImage:(nullable UIImage *)placeholder
                           options:(SDWebImageOptions)options
                      operationKey:(nullable NSString *)operationKey
                     setImageBlock:(nullable SDSetImageBlock)setImageBlock
                          progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
                         completed:(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 *)url
                                          options:(SDWebImageOptions)options
                                          context:(nullable SDWebImageContext *)context
                                         progress:(nullable SDImageLoaderProgressBlock)progressBlock
                                        completed:(nonnull SDInternalCompletionBlock)completedBlock {

    // 确保已完成回调块不为空,否则调用此方法无意义
    NSAssert(completedBlock != nil, @"若要预取图像,请使用-[SDWebImagePrefetcher prefetchURLs]方法");

    // 处理URL类型,允许传入NSString并转换为NSURL
    if ([url isKindOfClass:NSString.class]) {
        url = [NSURL URLWithString:(NSString *)url];
    }

    // 防止传入非NSURL类型的无效URL(如NSNull),将其置为nil
    if (![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 *)operation
                                 url:(nonnull NSURL *)url
                             options:(SDWebImageOptions)options
                             context:(nullable SDWebImageContext *)context
                            progress:(nullable SDImageLoaderProgressBlock)progressBlock
                           completed:(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 *)key
                                                options:(SDImageCacheOptions)options
                                               context:(nullable SDWebImageContext *)context
                                            cacheType:(SDImageCacheType)queryCacheType
                                                  done:(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_MAC
                    image = [[NSImage alloc] initWithCGImage:image.CGImage scale:image.scale orientation:kCGImagePropertyOrientationUp];
                #else
                    image = [[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) { // 图片已从内存缓存获取,仅需根据数据生成UIImage
            diskImage = image;
        } else if (diskData) { // 从磁盘缓存获取到数据,需解析为UIImage
            BOOL 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_MAC
                    thumbnailSize = thumbnailSizeValue.sizeValue;
                #else
                    thumbnailSize = 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 *)operation
                                    url:(nonnull NSURL *)url
                                options:(SDWebImageOptions)options
                                context:(SDWebImageContext *)context
                            cachedImage:(nullable UIImage *)cachedImage
                             cachedData:(nullable NSData *)cachedData
                              cacheType:(SDImageCacheType)cacheType
                               progress:(nullable SDImageLoaderProgressBlock)progressBlock
                              completed:(nullable SDInternalCompletionBlock)completedBlock {

    // 标记缓存操作结束
    @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); // 强引用恢复operation

            if (!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 { // 图片下载成功

                // 如果允许重试失败,移除失败URL
                if (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 *)url
                                                   options:(SDWebImageDownloaderOptions)options
                                                   context:(nullable SDWebImageContext *)context
                                                  progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
                                                 completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock {

    // 如果URL为nil,立即调用完成回调并返回nil
    if (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.mfbz.cn/a/549667.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

思维导图ai生成软件分享5款好用的!

思维导图ai生成软件分享5款好用的&#xff01; 在快节奏的信息时代&#xff0c;思维导图作为一种有效的思维整理工具&#xff0c;越来越受到人们的青睐。它能够将复杂的思维过程可视化&#xff0c;帮助我们更好地梳理思路、规划工作。近年来&#xff0c;随着人工智能技术的飞速…

整数运算超越存储单元表示范围:上溢出、下溢出、回绕

示例&#xff1a; /*** brief how about integer-underflow-overflow? show you here.* author wenxuanpei* email 15873152445163.com(query for any question here)*/ #define _CRT_SECURE_NO_WARNINGS//support c-library in Microsoft-Visual-Studio #include <std…

408数据结构,怎么练习算法大题?

其实考研的数据结构算法题是有得分技巧的 得分要点 会写结构定义&#xff08;没有就自己写上&#xff09;写清楚解题的算法思想描述清楚算法实现最后写出时间和空间复杂度 以上这四步是完成一道算法题的基本步骤&#xff0c;也是其中得分的主要地方就是后面两步。但是前面两…

java-spring 图灵 04

在Spring框架中&#xff0c;可以使用org.springframework.core.io.support.ResourcePatternResolver接口的resolveBasePackage方法来将指定的基础包解析为用于包搜索路径的模式规范。 例如&#xff0c;如果基础包是com.example.app&#xff0c;则可以使用resolveBasePackage方法…

【深度学习】【机器学习】用神经网络进行入侵检测,NSL-KDD数据集,基于机器学习(深度学习)判断网络入侵,网络攻击,流量异常【3】

之前用NSL-KDD数据集做入侵检测的项目是&#xff1a; 【1】https://qq742971636.blog.csdn.net/article/details/137082925 【2】https://qq742971636.blog.csdn.net/article/details/137170933 有人问我是不是可以改代码&#xff0c;我说可以。 训练 我将NSL_KDD_Final_1.i…

Open3D 无效点滤波(32)

Open3D 无效点滤波(32) 一、算法介绍二、算法实现1.代码2.效果一、算法介绍 这个算法的目标是从点云数据中去除无效的点,这些无效点可能是由于坐标值为无穷大(inf)或者不是数字(NaN)而产生的。这些无效点可能会导致后续处理步骤出现错误或异常,因此在处理点云数据时需…

品深茶创始人是谁?

据说&#xff0c;品深茶的创始人之前是一个程序员&#xff0c;他在软件行业工作十多年&#xff0c;由于常年熬夜加班再加上抽烟喝酒等不良习惯&#xff0c;导致在一次体检中被查出患上了肾癌&#xff0c;对他来说&#xff0c;期待的财务自由还没实现&#xff0c;身体就已经完蛋…

java(网络编程)

什么是网络编程? 在网络通信协议下&#xff0c;不同计算机上运行的程序&#xff0c;进行的数据传输。 应用场景&#xff1a;即时通信、网游对战、金融证券、国际贸易、邮件、等等 不管是什么场景&#xff0c;都是计算机跟计算机之间通过网络进行数据传输 Java中可以使用ja…

CSS基础:width,height尺寸属性详解

你好&#xff0c;我是云桃桃。 一个希望帮助更多朋友快速入门 WEB 前端的程序媛。云桃桃&#xff0c;大专生&#xff0c;一枚程序媛&#xff0c;感谢关注。回复 “前端基础题”&#xff0c;可免费获得前端基础 100 题汇总&#xff0c;回复 “前端工具”&#xff0c;可获取 Web…

vue脚手架CLI的简单使用

目录 初始化脚手架说明具体步骤模板项目的结构main.js入口文件app.vuemain.jsrender main.js 修改默认配置 初始化脚手架 说明 Vue 脚手架是 Vue 官方提供的标准化开发工具&#xff08;开发平台&#xff09;。最新的版本是 4.x。文档: https://cli.vuejs.org/zh/。 具体步骤 …

QFS [VLDB‘13] 论文阅读笔记

原论文&#xff1a;The Quantcast File System (VLDB’13) QFS简介及技术要点 QFS&#xff08;Quantcast File System&#xff09;是由Quantcast开发的一个高效、可扩展的分布式文件系统&#xff0c;旨在提供与Hadoop分布式文件系统&#xff08;HDFS&#xff09;兼容的替代方案…

allure2教程-1-环境搭建

领取资料&#xff0c;咨询答疑&#xff0c;请➕wei: June__Go 自动化测试执行完成后我们需要展示给其他人看&#xff0c;这就要有自动化测试报告了。复杂的测试报告当然可以自己代码实现&#xff0c;但用pytest-html或allure基本也能满足我们生成测试报告的要求了。本小节介绍…

如何基于香橙派AIpro将开源框架模型转换为昇腾模型

在前面的介绍中&#xff0c;我们知道了如何基于香橙派AIpro开发AI推理应用&#xff0c;也大致了解到在推理之前&#xff0c;需要把原始网络模型 (可能是 PyTorch 的、TensorFlow&#xff0c;可能是Caffe的等等) 转换成 .om 模型&#xff0c;然后才能调用昇腾的aclmdlExecute 等…

深度学习 Lecture 8 决策树

一、决策树模型&#xff08;Decision Tree Model) 椭圆形代表决策节点&#xff08;decison nodes)&#xff0c;矩形节点代表叶节点&#xff08;leaf nodes)&#xff0c;方向上的值代表属性的值&#xff0c; 构建决策树的学习过程&#xff1a; 第一步&#xff1a;决定在根节点…

Blender3.3 下载地址及安装教程

Blender是一款开源的3D计算机图形软件&#xff0c;广泛应用于动画制作、游戏开发、建模、渲染等领域。它提供了一套强大的工具和功能&#xff0c;让用户能够进行三维建模、动画制作和视觉效果的创作。 Blender支持多种文件格式的导入和导出&#xff0c;使用户能够与其他软件进…

微博聚类文本分析和可视化

本文使用python抓取微博数据并对微博文本分析和可视化&#xff0c;LDA&#xff08;树图&#xff09;、关系图、词云、时间趋势&#xff08;折线图&#xff09;、热度地图、词典情感分析&#xff08;饼图和3D柱状图&#xff09;、词向量神经网络情感分析、tfidf聚类、词向量聚类…

家居网购项目(手写分页)

文章目录 1.后台管理—分页显示1.程序框架图2.编写数据模型Page.java 3.编写dao层1.修改FurnDao增加方法 2.修改FurnDaoImpl增加方法 3.单元测试FurnDaoTest 4.编写service层1.修改FurnService增加方法 2.修改FurnServiceImpl增加方法3.单元测试FurnServiceTest 5.编写DataUtil…

新版AndroidStudio使用switch-case语句时出现Constant expression required错误

原因: 在新版的Android Studio中使用JDK17以上版本&#xff0c;会出现switch语句报错"Constant expression required"的问题&#xff0c;这是因为在JDK17中switch语句的条件表达式支持使用枚举类型&#xff0c;而这个特性还没有被支持。 解决方法: ①在gradle.prope…

pytorch 今日小知识3——nn.MaxPool3d 、nn.AdaptiveAvgPool3d、nn.ModuleList

MaxPool3d — PyTorch 2.2 documentation 假设输入维度&#xff08;1,2,3,4,4&#xff09; maxpool torch.nn.MaxPool3d(kernel_size(2, 2, 2), stride(2, 2, 2), padding(1, 0, 0))F 维的 kernel_size 为 2&#xff0c;说明在 F 维的覆盖的 frame 数为 2&#xff0c;也就是…

ElasticSearch查询时修改打分

原生的ES打分基于BM25算法&#xff0c;相比于TF-IDF已经有了较大的改进&#xff0c;但是在实际场景中往往最终的排序效果还是需要进行调整。由于直接修改索引的权重往往代价较大&#xff0c;比较经济的方式还是在查询时即时修改得分以实现排序控制。 注&#xff1a;案例测试数据…
最新文章