SDWebImage源码学习

发布于:2025-05-23 ⋅ 阅读:(15) ⋅ 点赞:(0)

SDWebImage源码学习

前言

SDWebImage的具体流程图
img

宏定义

#ifndef dispatch_main_async_safe
#define dispatch_main_async_safe(block)\
    if (dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL) == dispatch_queue_get_label(dispatch_get_main_queue())) {\
        block();\
    } else {\
        dispatch_async(dispatch_get_main_queue(), block);\
    }

//弃用声明
#pragma clang deprecated(dispatch_main_async_safe, "Use SDCallbackQueue instead")
#endif

就是将block的回调放在了主线程之中,避免了线程错误,在新的版本之中我们使用SDCallbackQueue进行调用

// 旧方案(已弃用)
dispatch_main_async_safe(^{
    self.image = image;
});

// 新方案(推荐)
SDCallbackQueue *mainQueue = [SDCallbackQueue mainQueue];
[mainQueue async:^{
    self.image = image;
}];

SDWebImageOptions

SDWebImageOptions 是 SDWebImage 框架中用于 配置图片加载与缓存策略 的核心枚举类型,通过位掩码组合多种选项,控制图片下载、缓存、解码等行为的细节。

1. 核心选项

1.1 SDWebImageRetryFailed

  • 作用:默认情况下,下载失败的 URL 会被加入黑名单,后续不再重试。启用此选项后,即使下载失败仍会尝试重新下载。
  • 适用场景:需多次重试的临时网络错误场景。

1.2 SDWebImageLowPriority

  • 作用:禁止在 UI 交互(如 UIScrollView 滚动)时下载图片,改为在滚动减速时再启动下载。
  • 适用场景:优化列表滚动时的性能。

1.3 SDWebImageProgressiveLoad

  • 作用:启用渐进式加载,图片边下载边逐行显示(类似浏览器)。
  • 适用场景:大图加载时提升用户体验。

2. 缓存策略

2.1 SDWebImageRefreshCached

  • 作用:即使本地有缓存,仍根据 HTTP 缓存策略重新下载图片(使用 NSURLCache 而非 SDWebImage 的磁盘缓存)。
  • 适用场景:URL 不变但图片内容频繁更新的场景(如用户头像)。

** SDWebImageFromCacheOnly**

  • 作用:仅从缓存加载图片,不触发网络请求。
  • 适用场景:离线模式或强制使用缓存时。

2.3 SDWebImageFromLoaderOnly

  • 作用:仅从网络加载图片,忽略缓存。
  • 适用场景:需强制刷新图片的场景。

3. 安全与后台

3.1 SDWebImageHandleCookies

  • 作用:处理 NSHTTPCookieStore 中的 Cookies,通过设置 NSMutableURLRequest.HTTPShouldHandleCookies = YES 实现。
  • 适用场景:需要维护会话状态的请求。

3.2 SDWebImageAllowInvalidSSLCertificates

  • 作用:允许不受信任的 SSL 证书。
  • 适用场景:测试环境调试,生产环境慎用。

3.3 SDWebImageContinueInBackground

  • 作用:应用进入后台后继续下载(需系统支持后台任务)。
  • 适用场景:长时间下载大图的场景。

4. 优先级与占位图

4.1 SDWebImageHighPriority

  • 作用:将下载任务提升至队列最前端,立即执行。
  • 适用场景:关键图片的优先加载。

4.2 SDWebImageDelayPlaceholder

  • 作用:延迟显示占位图,直到图片下载完成。
  • 适用场景:避免占位图与加载完成图切换时的闪烁。

5. 图片处理

5.1 SDWebImageTransformAnimatedImage

  • 作用:允许对动画图片(如 GIF)应用变换操作(如裁剪、滤镜)。
  • 适用场景:需对动图进行自定义处理的场景。

5.2 SDWebImageAvoidAutoSetImage

  • 作用:阻止自动设置图片到视图,需在 completedBlock 中手动处理。
  • 适用场景:需自定义动画(如淡入淡出)或后处理的场景。

5.3 SDWebImageScaleDownLargeImages

  • 作用:自动缩小大图以适应设备内存限制(默认限制 60MB)。
  • 适用场景:内存敏感型应用,防止 OOM。

6. 高级查询

6.1 SDWebImageQueryMemoryData

  • 作用:强制查询内存中的图片数据(默认仅缓存 UIImage 对象)。
  • 适用场景:需直接操作原始图片数据的场景。

6.2 SDWebImageQueryMemoryDataSync / SDWebImageQueryDiskDataSync

  • 作用:同步查询内存或磁盘数据(默认异步)。
  • 适用场景:需避免单元格重用闪烁的场景(慎用,可能阻塞主线程)。

7. 过渡与动画

7.1 SDWebImageForceTransition

  • 作用:强制应用 SDWebImageTransition 过渡动画到缓存图片(默认仅网络下载触发)。
  • 适用场景:统一缓存与网络图片的展示效果。

7.2 SDWebImageDecodeFirstFrameOnly

  • 作用:仅解码动图的首帧,生成静态图片。
  • 适用场景:仅需显示动图首帧的场景。

7.3 SDWebImagePreloadAllFrames

  • 作用:预加载动图所有帧到内存(减少渲染时的 CPU 开销)。
  • 适用场景:多个视图共享同一动图的场景。

8. 其他选项

8.1 SDWebImageAvoidAutoCancelImage

  • 作用:禁用自动取消同一视图的旧图片加载请求。
  • 适用场景:需同时加载多张图片到同一视图的不同区域。

8.2 SDWebImageWaitStoreCache

  • 作用:等待图片数据完全写入磁盘后再回调。
  • 适用场景:需确保缓存写入完成的后续操作。

SDWebImageContext

键(Key) 值类型 功能与作用场景
SDWebImageContextSetImageOperationKey NSString 操作唯一性控制:唯一标识加载任务,解决同一视图(如 UIButton 不同状态)多次加载时的任务冲突。默认使用视图类名生成唯一键。
SDWebImageContextImageCache id<SDImageCache> 缓存实例替换:使用自定义缓存实例(如分业务隔离缓存),覆盖全局默认的 SDImageCache.sharedImageCache
SDWebImageContextImageLoader id<SDImageLoader> 下载器替换:自定义网络请求逻辑(如加密传输、代理配置),替代默认的 SDWebImageDownloader
SDWebImageContextImageDecodeOptions SDImageCoderOptions 解码优化:传递缩略图尺寸(SDImageCoderDecodeThumbnailPixelSize)或渐进式解码参数,减少内存占用并提升渲染性能。
SDWebImageContextCallbackQueue SDCallbackQueue 线程安全控制:指定回调执行队列(如主队列或串行队列),确保 UI 更新在主线程执行。
SDWebImageContextStoreCacheType SDImageCacheType 缓存策略覆盖:强制指定存储方式(内存/磁盘/不存储),优先级高于全局配置。例如敏感图片仅缓存在内存中。
SDWebImageContextImageTransformer id<SDImageTransformer> 图片变换:加载完成后自动应用变换(如圆角裁剪、滤镜),变换后的图片会独立缓存。需实现 SDImageTransformer 协议。
SDWebImageContextTransition SDWebImageTransition 过渡动画:定义图片加载完成后的动画效果(如淡入、缩放),增强用户体验。
SDWebImageContextDownloadRequestModifier id<SDWebImageDownloaderRequestModifier> 请求修改:动态修改下载请求,例如添加 HTTP 头、调整超时时间或重定向策略。

SDOperationsDictionary

typedef NSMapTable<NSString *, id<SDWebImageOperation>> SDOperationsDictionary;

SDOperationsDictionary继承于NSMapTab,当视图发起新的图片加载请求时,SDOperationsDictionary会通过唯一标识符(如 validOperationKey)查找并取消当前视图关联的未完成操作,

维度 NSDictionary NSMapTable
Key 类型 必须实现 NSCopying 的 Objective-C 对象 任意指针(包括非对象)
内存管理 Key 拷贝 + Value 强引用 可配置强/弱引用或拷贝行为
适用场景 静态键值存储、高频读取 弱引用缓存、非标准键值映射
线程安全
哈希冲突处理 链表法 可定制(依赖初始化参数)

入口函数

image-20250517222824420

我们看到,无论如何调用哪一个函数,最终都会进入

- (void)sd_setImageWithURL:(nullable NSURL *)url
          placeholderImage:(nullable UIImage *)placeholder
                   options:(SDWebImageOptions)options
                  progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
                 completed:(nullable SDExternalCompletionBlock)completedBlock;

进入原函数看一下

- (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 {
    
    // Very common mistake is to send the URL using NSString object instead of NSURL. For some strange reason, Xcode won't
    // throw any warning for this type mismatch. Here we failsafe this error by allowing URLs to be passed as NSString.
    //  if url is NSString and shouldUseWeakMemoryCache is true, [cacheKeyForURL:context] will crash. just for a  global protect.
    
    //支持NSString类型
    if ([url isKindOfClass:NSString.class]) {
        url = [NSURL URLWithString:(NSString *)url];
    }
    // Prevents app crashing on argument type error like sending NSNull instead of NSURL
    if (![url isKindOfClass:NSURL.class]) {
        url = nil;
    }
    
                                                     /
    if (context) {
         //复制防止可变对象 
        context = [context copy];
    } else {
        context = [NSDictionary dictionary];
    }
                                                       
   //每个视图(如 UIImageView、UIButton)的图片加载操作通过 validOperationKey 唯一标识。例如,同一视图中可能同时加载背景图与图标(即不同的state),通过不同键值区分任务。                                     
    NSString *validOperationKey = context[SDWebImageContextSetImageOperationKey];
    if (!validOperationKey) {
        //如果上下文传入时,没有制定SDWebImageContextSetImageOperationKey之中的内容,SDWebImage 将使用视图类名(如 NSStringFromClass([self class]))作为默认键值
        validOperationKey = NSStringFromClass([self class]);
        SDWebImageMutableContext *mutableContext = [context mutableCopy];
        mutableContext[SDWebImageContextSetImageOperationKey] = validOperationKey;
        context = [mutableContext copy];
    }
    self.sd_latestOperationKey = validOperationKey;
    //当启动新的图片加载请求时,若未显式指定 SDWebImageAvoidAutoCancelImage 选项,自动取消同一视图实例的旧图片加载任务
    if (!(SD_OPTIONS_CONTAINS(options, SDWebImageAvoidAutoCancelImage))) {
      //之中的队列任务是通过SDOperationsDictionary,这个SDOperationsDictionary继承于NSMapTable,NSMapTable
        [self sd_cancelImageLoadOperationWithKey:validOperationKey];
    }
         
    //通过 SDWebImageLoadState 对象记录当前视图的图片加载状态(如请求 URL、进度、错误信息等)
    SDWebImageLoadState *loadState = [self sd_imageLoadStateForKey:validOperationKey];                         if (!loadState) {
        loadState = [SDWebImageLoadState new];
    }
    loadState.url = url;
    [self sd_setImageLoadState:loadState forKey:validOperationKey];
    
 //分配manager
    SDWebImageManager *manager = context[SDWebImageContextCustomManager];
    if (!manager) {
        manager = [SDWebImageManager sharedManager];
    } else {
        // 删除manager避免循环引用 (manger -> loader -> operation -> context -> manager)
        SDWebImageMutableContext *mutableContext = [context mutableCopy];
        mutableContext[SDWebImageContextCustomManager] = nil;
        context = [mutableContext copy];
    }
                                                       
   //获取回调队列
    SDCallbackQueue *queue = context[SDWebImageContextCallbackQueue];
    BOOL shouldUseWeakCache = NO;
    if ([manager.imageCache isKindOfClass:SDImageCache.class]) {
        shouldUseWeakCache = ((SDImageCache *)manager.imageCache).config.shouldUseWeakMemoryCache;
    }
    if (!(options & SDWebImageDelayPlaceholder)) {
        if (shouldUseWeakCache) {
            NSString *key = [manager cacheKeyForURL:url context:context];
            // call memory cache to trigger weak cache sync logic, ignore the return value and go on normal query
            // this unfortunately will cause twice memory cache query, but it's fast enough
            // in the future the weak cache feature may be re-design or removed
            [((SDImageCache *)manager.imageCache) imageFromMemoryCacheForKey:key];
        }
      //如果没有获取到指定队列,就在主队列之中运行,加载placeholder
        [(queue ?: SDCallbackQueue.mainQueue) async:^{
            [self sd_setImage:placeholder imageData:nil basedOnClassOrViaCustomSetImageBlock:setImageBlock cacheType:SDImageCacheTypeNone imageURL:url];
        }];
    }
    //SDWebImageOperation 是继承于NSOperation,支持取消功能的任务队列
    id <SDWebImageOperation> operation = nil;
    
    if (url) {
        // 重置下载进程
        NSProgress *imageProgress = loadState.progress;
        if (imageProgress) {
            imageProgress.totalUnitCount = 0;
            imageProgress.completedUnitCount = 0;
        }
        
#if SD_UIKIT || SD_MAC
        // 检查是否有指示器并启用
        [self sd_startImageIndicatorWithQueue:queue];
        id<SDWebImageIndicator> imageIndicator = self.sd_imageIndicator;
#endif
        
        SDImageLoaderProgressBlock combinedProgressBlock = ^(NSInteger receivedSize, NSInteger expectedSize, NSURL * _Nullable targetURL) {
            if (imageProgress) {
                imageProgress.totalUnitCount = expectedSize;
                imageProgress.completedUnitCount = receivedSize;
            }
#if SD_UIKIT || SD_MAC
            if ([imageIndicator respondsToSelector:@selector(updateIndicatorProgress:)]) {
                double progress = 0;
                if (expectedSize != 0) {
                    progress = (double)receivedSize / expectedSize;
                }
                progress = MAX(MIN(progress, 1), 0); // 0.0 - 1.0
                dispatch_async(dispatch_get_main_queue(), ^{
                    [imageIndicator updateIndicatorProgress:progress];
                });
            }
#endif
            if (progressBlock) {
                progressBlock(receivedSize, expectedSize, targetURL);
            }
        };
      
      //通过 @weakify/@strongify 宏避免 Block 强引用导致的内存泄漏,加载图片
        @weakify(self);
        operation = [manager loadImageWithURL:url options:options context:context progress:combinedProgressBlock completed:^(UIImage *image, NSData *data, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
            @strongify(self);
            if (!self) { return; }
            // if the progress not been updated, mark it to complete state
            if (imageProgress && finished && !error && imageProgress.totalUnitCount == 0 && imageProgress.completedUnitCount == 0) {
                imageProgress.totalUnitCount = SDWebImageProgressUnitCountUnknown;
                imageProgress.completedUnitCount = SDWebImageProgressUnitCountUnknown;
            }
            
#if SD_UIKIT || SD_MAC
            // check and stop image indicator
            if (finished) {
                [self sd_stopImageIndicatorWithQueue:queue];
            }
#endif
            //根据选项控制是否自动设置图片或延迟占位符。
            BOOL shouldCallCompletedBlock = finished || (options & SDWebImageAvoidAutoSetImage);
            BOOL shouldNotSetImage = ((image && (options & SDWebImageAvoidAutoSetImage)) ||
                                      (!image && !(options & SDWebImageDelayPlaceholder)));
            SDWebImageNoParamsBlock callCompletedBlockClosure = ^{
                if (!self) { return; }
                if (!shouldNotSetImage) {
                    [self sd_setNeedsLayout];
                }
                if (completedBlock && shouldCallCompletedBlock) {
                    completedBlock(image, data, error, cacheType, finished, url);
                }
            };
            
            // case 1a: we got an image, but the SDWebImageAvoidAutoSetImage flag is set
            // OR
            // case 1b: we got no image and the SDWebImageDelayPlaceholder is not set
            if (shouldNotSetImage) {
                [(queue ?: SDCallbackQueue.mainQueue) async:callCompletedBlockClosure];
                return;
            }
            
            UIImage *targetImage = nil;
            NSData *targetData = nil;
            if (image) {
                // case 2a: we got an image and the SDWebImageAvoidAutoSetImage is not set
                targetImage = image;
                targetData = data;
            } else if (options & SDWebImageDelayPlaceholder) {
                // case 2b: we got no image and the SDWebImageDelayPlaceholder flag is set
                targetImage = placeholder;
                targetData = nil;
            }
            
#if SD_UIKIT || SD_MAC
          
           // 确定最终显示的图像(网络图片/占位符)并应用过渡动画。
            SDWebImageTransition *transition = nil;
            BOOL shouldUseTransition = NO;
            if (options & SDWebImageForceTransition) {
                // Always
                shouldUseTransition = YES;
            } else if (cacheType == SDImageCacheTypeNone) {
                // From network
                shouldUseTransition = YES;
            } else {
                // From disk (and, user don't use sync query)
                if (cacheType == SDImageCacheTypeMemory) {
                    shouldUseTransition = NO;
                } else if (cacheType == SDImageCacheTypeDisk) {
                    if (options & SDWebImageQueryMemoryDataSync || options & SDWebImageQueryDiskDataSync) {
                        shouldUseTransition = NO;
                    } else {
                        shouldUseTransition = YES;
                    }
                } else {
                    // Not valid cache type, fallback
                    shouldUseTransition = NO;
                }
            }
            if (finished && shouldUseTransition) {
                transition = self.sd_imageTransition;
            }
#endif
            [(queue ?: SDCallbackQueue.mainQueue) async:^{
#if SD_UIKIT || SD_MAC
                [self sd_setImage:targetImage imageData:targetData options:options basedOnClassOrViaCustomSetImageBlock:setImageBlock transition:transition cacheType:cacheType imageURL:imageURL callback:callCompletedBlockClosure];
#else
                [self sd_setImage:targetImage imageData:targetData basedOnClassOrViaCustomSetImageBlock:setImageBlock cacheType:cacheType imageURL:imageURL];
                callCompletedBlockClosure();
#endif
            }];
        }];
      //加入任务队列
        [self sd_setImageLoadOperation:operation forKey:validOperationKey];
    } else {
#if SD_UIKIT || SD_MAC
      //url不存在
        [self sd_stopImageIndicatorWithQueue:queue];
#endif
        if (completedBlock) {
          //返回错误信息
            [(queue ?: SDCallbackQueue.mainQueue) async:^{
                NSError *error = [NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorInvalidURL userInfo:@{NSLocalizedDescriptionKey : @"Image url is nil"}];
                completedBlock(nil, nil, error, SDImageCacheTypeNone, YES, url);
            }];
        }
    }
    
    return operation;
}

SDOperationsDictionary(在 SDWebImage 中实际名为 operationDictionary)是用于 管理视图(如 UIImageViewUIButton)关联的图片加载操作的核心数据结构。它的作用主要体现在以下几个方面:


1. 操作唯一性与冲突避免

  • 取消旧任务:当视图发起新的图片加载请求时,operationDictionary 会通过唯一标识符(如 validOperationKey)查找并取消当前视图关联的未完成操作。例如:

    [self sd_cancelImageLoadOperationWithKey:validOperationKey];
    

    这确保同一视图不会因重复请求(如快速滑动列表时)导致图片错位或资源浪费。

  • 多操作共存:支持不同操作键区分同一视图的多个独立任务(如同时加载头像和背景图)。


2. 线程安全与生命周期管理

  • 线程安全存储:通过关联对象(Associated Object)动态挂载到 UIView 上,使用 NSMapTableNSMutableDictionary 存储操作,确保多线程环境下操作增删的安全性。
  • 自动释放机制:当视图释放时,关联的 operationDictionary 会自动清理未完成的操作,避免内存泄漏。

3. 操作键(Operation Key)机制

  • 灵活标识符:默认以视图类名(如 UIImageView)作为操作键,但也支持通过上下文(SDWebImageContext)自定义键,适应复杂场景。
  • 动态绑定:例如,同一 UIImageView 的不同加载任务(如普通状态与高亮状态)可通过不同键独立管理。

4. 与 SDWebImage 核心模块协作

  • 协调下载与缓存:通过 operationDictionary 管理 SDWebImageOperation 对象(如下载任务 SDWebImageDownloaderOperation),实现与 SDWebImageManagerSDImageCache 的交互。
  • 支持优先级控制:例如,在 UITableView 滑动时,通过取消低优先级任务优化性能。

工具层

从上面UIKit+WebCache暴露的接口函数来看,我们会发现图片的加载核心其实是loadImageWithURL这个load函数,我们可以看到这个函数是在SDWebImageManager之中

- (SDWebImageCombinedOperation *)loadImageWithURL:(nullable NSURL *)url
                                          options:(SDWebImageOptions)options
                                          context:(nullable SDWebImageContext *)context
                                         progress:(nullable SDImageLoaderProgressBlock)progressBlock
                                        completed:(nonnull SDInternalCompletionBlock)completedBlock {
    // Invoking this method without a completedBlock is pointless
    NSAssert(completedBlock != nil, @"If you mean to prefetch the image, use -[SDWebImagePrefetcher prefetchURLs] instead");

    // 这里我们通过允许将 URL 作为 NSString 传递来防止这类错误
    if ([url isKindOfClass:NSString.class]) {
        url = [NSURL URLWithString:(NSString *)url];
    }

    // 防止因参数类型错误(如传递 NSNull 而非 NSURL)导致应用崩溃
    if (![url isKindOfClass:NSURL.class]) {
        url = nil;
    }

    SDWebImageCombinedOperation *operation = [SDWebImageCombinedOperation new];
    operation.manager = self;
//两个信号量分别控制操作failedURLs和runningOperations的线程安全。信号量为一,实现锁的效果
    BOOL isFailedUrl = NO;
    if (url) {
        SD_LOCK(_failedURLsLock);
        isFailedUrl = [self.failedURLs containsObject:url];
        SD_UNLOCK(_failedURLsLock);
    }
    
 		 // 预处理 options 和 context 参数,为管理器决定最终结果
    SDWebImageOptionsResult *result = [self processedResultForURL:url options:options context:context];
//url的绝对字符串长度为0或者(在不禁用黑名单的情况下)并且Url在黑名单内,发出警告提示
    if (url.absoluteString.length == 0 || (!(options & SDWebImageRetryFailed) && isFailedUrl)) {
        NSString *description = isFailedUrl ? @"Image url is blacklisted" : @"Image url is nil";
        NSInteger code = isFailedUrl ? SDWebImageErrorBlackListed : SDWebImageErrorInvalidURL;
       //调用completionBlock块,结束该函数
        [self callCompletionBlockForOperation:operation completion:completedBlock error:[NSError errorWithDomain:SDWebImageErrorDomain code:code userInfo:@{NSLocalizedDescriptionKey : description}] queue:result.context[SDWebImageContextCallbackQueue] url:url];
        return operation;
    }
		//将这个操作加入进程
    SD_LOCK(_runningOperationsLock);
    [self.runningOperations addObject:operation];
    SD_UNLOCK(_runningOperationsLock);
    
   // 开始从缓存加载图片的入口,主要步骤如下:
    // 无转换器的步骤:
    // 1. 从缓存查询图片,未命中
    // 2. 下载数据并生成图片
    // 3. 将图片存储至缓存
    
    // 带转换器的步骤:
    // 1. 从缓存查询转换后的图片,未命中
    // 2. 从缓存查询原始图片,未命中
    // 3. 下载数据并生成图片
    // 4. 在 CPU 中执行转换
    // 5. 将原始图片存储至缓存
    // 6. 将转换后的图片存储至缓存
    [self callCacheProcessForOperation:operation url:url options:result.options context:result.context progress:progressBlock completed:completedBlock];

    return operation;
}

SDWebImageManager是一个单例,作用就是调度SDImageCache和SDWebImageDownloader进行缓存和下载操作的。

我们的程序为了避免在查看缓存和下载两个部分代码进行耦合,所用使用的SDWebImageCombinedOperation将缓存与下载的协作逻辑内聚到单一对象中,提升代码可维护性。

缓存查找

我们在loadImageWithURL可以看到,最后程序进入了[self callCacheProcessForOperation:operation url:url options:result.options context:result.context progress:progressBlock completed:completedBlock];的方法之中,看名字就能大概知道,这个函数就是用来查找缓存的。

SDImageCacheConfig

用于存取缓存配置

static const NSInteger kDefaultCacheMaxCacheAge = 60 * 60 * 24 * 7; // 1 week

@implementation SDImageCacheConfig

- (instancetype)init {
    if (self = [super init]) {
        _shouldDecompressImages = YES;
        _shouldDisableiCloud = YES;
        _shouldCacheImagesInMemory = YES;
        _shouldUseWeakMemoryCache = YES;
        _diskCacheReadingOptions = 0;
        _diskCacheWritingOptions = NSDataWritingAtomic;
        _maxCacheAge = kDefaultCacheMaxCacheAge;
        _maxCacheSize = 0;
        _diskCacheExpireType = SDImageCacheConfigExpireTypeModificationDate;
    }
    return self;
}

SDImageCacheType
typedef NS_ENUM(NSInteger, SDImageCacheType) {
    //从网上下载的
    SDImageCacheTypeNone,
    //从磁盘中获取的
    SDImageCacheTypeDisk,
    //从内存中获取的
    SDImageCacheTypeMemory,
    SDImageCacheTypeAll
};


processedResultForURL
- (SDWebImageOptionsResult *)processedResultForURL:(NSURL *)url 
                                          options:(SDWebImageOptions)options 
                                          context:(SDWebImageContext *)context {
    // 初始化结果对象和可变上下文
    SDWebImageOptionsResult *result;
    SDWebImageMutableContext *mutableContext = [SDWebImageMutableContext dictionary];
    
    // 1. 补充默认的图片转换器
    if (!context[SDWebImageContextImageTransformer]) {
        id<SDImageTransformer> transformer = self.transformer;
        [mutableContext setValue:transformer forKey:SDWebImageContextImageTransformer];
    }
    // 2. 补充默认的缓存键生成规则
    if (!context[SDWebImageContextCacheKeyFilter]) {
        id<SDWebImageCacheKeyFilter> cacheKeyFilter = self.cacheKeyFilter;
        [mutableContext setValue:cacheKeyFilter forKey:SDWebImageContextCacheKeyFilter];
    }
    // 3. 补充默认的缓存序列化器
    if (!context[SDWebImageContextCacheSerializer]) {
        id<SDWebImageCacheSerializer> cacheSerializer = self.cacheSerializer;
        [mutableContext setValue:cacheSerializer forKey:SDWebImageContextCacheSerializer];
    }
    
    // 4. 合并用户自定义上下文与默认配置
    if (mutableContext.count > 0) {
        if (context) {
            [mutableContext addEntriesFromDictionary:context]; // 保留用户自定义优先级
        }
        context = [mutableContext copy]; // 生成最终不可变上下文
    }
    
    // 5. 应用参数处理器(动态修改选项或上下文)
    if (self.optionsProcessor) {
        result = [self.optionsProcessor processedResultForURL:url 
                                                      options:options 
                                                      context:context];
    }
    // 6. 若无自定义处理器,生成默认结果
    if (!result) {
        result = [[SDWebImageOptionsResult alloc] initWithOptions:options 
                                                          context:context];
    }
    
    return result;
}

生成的 result 对象用于后续 callCacheProcessForOperation: 缓存查询和 callDownloadProcessForOperation: 下载流程。

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];
    }
    
    // 检查是否需要查询缓存(若未设置 SDWebImageFromLoaderOnly 选项则需查询)
    BOOL shouldQueryCache = !SD_OPTIONS_CONTAINS(options, SDWebImageFromLoaderOnly);
    if (shouldQueryCache) {
        // 生成转换后的缓存键(避免缩略图参数影响缓存键生成)
        NSString *key = [self cacheKeyForURL:url context:context];
        // 剥离缩略图相关参数,防止缓存键不匹配
        SDWebImageMutableContext *mutableContext = [context mutableCopy];
        mutableContext[SDWebImageContextImageThumbnailPixelSize] = nil;    // 清除缩略图尺寸
        mutableContext[SDWebImageContextImagePreserveAspectRatio] = nil;    // 清除宽高比保留标记
        @weakify(operation);  // 弱引用操作对象防止循环引用
      
      
        operation.cacheOperation = [imageCache queryImageForKey:key options:options context:mutableContext cacheType:queryCacheType completion:^(UIImage * _Nullable cachedImage, NSData * _Nullable cachedData, SDImageCacheType cacheType) {
            @strongify(operation);  // 强引用操作对象确保执行期间不被释放
            if (!operation || operation.isCancelled) {
                // 操作被用户取消:触发取消错误回调并清理资源
                [self callCompletionBlockForOperation:operation completion:completedBlock error:[NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorCancelled userInfo:@{NSLocalizedDescriptionKey : @"用户在查询缓存期间取消了操作"}] 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];
    }
}
cacheKeyForURL 和 originalCacheKeyForURL

在 SDWebImage 的缓存流程中,cacheKeyForURLoriginalCacheKeyForURL 生成的缓存键有以下核心区别:

  • cacheKeyForURL
    用于生成 转换后的缓存键,通常包含图片的 处理参数(如缩略图尺寸、裁剪模式、压缩质量等)。例如,若 URL 中包含 thumbnail=300x300quality=80 等参数,这些参数会影响最终生成的缓存键。
    • 目的:确保不同处理参数的图片在缓存中独立存储,避免显示错误(如不同尺寸的缩略图混用)。
// 示例:包含缩略图尺寸参数的键生成
NSString *key = [SDWebImageManager.sharedManager cacheKeyForURL:url context:@{SDWebImageContextImageThumbnailPixelSize: @(CGSizeMake(300, 300))}];
  • originalCacheKeyForURL
    生成 原始缓存键剥离所有图片处理参数,仅保留 URL 的 核心标识(如基础路径、固定参数)。例如,即使 URL 包含 thumbnail=300x300,原始缓存键会忽略该参数,仅基于原图 URL 生成键值。
    • 目的:支持从原始图片本地裁剪或转换,避免重复下载未处理的原图。
// 示例:剥离转换参数后的键生成
SDWebImageMutableContext *mutableContext = [context mutableCopy];
mutableContext[SDWebImageContextImageThumbnailPixelSize] = nil;
NSString *originKey = [self originalCacheKeyForURL:url context:mutableContext];

通过这种设计,SDWebImage 实现了 高效缓存复用灵活参数控制 的平衡。

SDImageCache

SDImageCache 是 SDWebImage 框架中 专门负责图片缓存管理的核心组件,其核心功能与设计模式如下:

  1. 自动缓存管理

    • 过期策略:默认清理超过一周(maxCacheAge 默认 7 天)的缓存文件。
    • 容量控制:可设置最大缓存大小(maxCacheSize),超出时按 LRU(最近最少使用)策略清理。
    • 后台清理:通过监听 UIApplicationDidEnterBackgroundNotification 等系统事件,在后台线程异步清理磁盘缓存。
  2. 线程安全与性能优化

    • 使用 dispatch_semaphore 控制对 NSMapTable 弱引用缓存的线程安全访问。
    • 图片解码优化:通过 SDWebImageDecoder 提前解压图片数据,避免主线程重复解码造成的性能损耗。
  3. 默认单例模式

    • 全局访问入口:通过 +[SDImageCache sharedImageCache] 获取单例实例,使用 dispatch_once 确保线程安全初始化。

    • 统一资源管理:单例模式便于集中控制内存和磁盘缓存,避免多实例导致的资源重复或状态不一致。

  4. 支持多实例场景

    • 命名空间隔离:可通过 initWithNamespace:diskCacheDirectory: 创建独立命名空间的缓存实例,实现不同内容的缓存隔离(如用户头像与商品图片分开存储)。

    • 灵活配置:每个实例可单独设置 maxCacheAgemaxCacheSize 等参数,适应不同缓存策略需求。

初始化方法

看一下初始化方法,理解

- (nonnull instancetype)initWithNamespace:(nonnull NSString *)ns
                       diskCacheDirectory:(nullable NSString *)directory
                                   config:(nullable SDImageCacheConfig *)config {
    if ((self = [super init])) {
        // 强校验:命名空间不能为空(用于缓存目录隔离)
        NSAssert(ns, @"缓存命名空间不能为空");
        
        // 配置对象处理(若未传入则使用默认配置)
        if (!config) {
            config = SDImageCacheConfig.defaultCacheConfig;
        }
        _config = [config copy];  // 深拷贝配置防止外部修改
        
        // 创建磁盘IO队列(串行队列保证线程安全),所有磁盘操作(读/写/删)在此队列执行以保证线程安全
        // 队列属性继承自config(可自定义优先级等属性)
        dispatch_queue_attr_t ioQueueAttributes = _config.ioQueueAttributes;
        _ioQueue = dispatch_queue_create("com.hackemist.SDImageCache.ioQueue", ioQueueAttributes);
        // 断言保证队列创建成功(防止错误的队列属性配置)
        NSAssert(_ioQueue, @"IO队列创建失败,请检查ioQueueAttributes配置");

        // 初始化内存缓存(使用配置指定的缓存类)
        // 自定义内存缓存类必须实现SDMemoryCache协议(如支持NSCache扩展)
        NSAssert([config.memoryCacheClass conformsToProtocol:@protocol(SDMemoryCache)],
                @"自定义内存缓存类必须遵循SDMemoryCache协议");
        _memoryCache = [[config.memoryCacheClass alloc] initWithConfig:_config];  // 注入配置

        // 处理磁盘缓存路径(三级目录结构)
        if (!directory) {
            // 默认路径:沙盒/Library/Caches/com.hackemist.SDWebImageCache
            directory = [self.class defaultDiskCacheDirectory];
        }
        // 拼接命名空间子目录(如default/avatar等业务隔离)
        _diskCachePath = [directory stringByAppendingPathComponent:ns];
        
        // 初始化磁盘缓存(支持自定义磁盘缓存实现)
        // 自定义类必须实现SDDiskCache协议(如加密磁盘存储)
        NSAssert([config.diskCacheClass conformsToProtocol:@protocol(SDDiskCache)],
                @"自定义磁盘缓存类必须遵循SDDiskCache协议");
        _diskCache = [[config.diskCacheClass alloc] initWithCachePath:_diskCachePath config:_config];
        
        // 执行缓存目录迁移(兼容旧版本路径结构)
        // 例如从无扩展名文件迁移到带扩展名版本
        [self migrateDiskCacheDirectory];  // 详见网页1的兼容处理

#if SD_UIKIT
        // 注册iOS应用生命周期通知
        // 1. 应用终止时:持久化缓存元数据(如过期时间)
        [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(applicationWillTerminate:)
                                                     name:UIApplicationWillTerminateNotification
                                                   object:nil];
        // 2. 进入后台时:触发异步清理过期缓存
        [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(applicationDidEnterBackground:)
                                                     name:UIApplicationDidEnterBackgroundNotification
                                                   object:nil];
#endif
#if SD_MAC
        // macOS平台的特殊处理(使用NSApplication生命周期)
        [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(applicationWillTerminate:)
                                                     name:NSApplicationWillTerminateNotification
                                                   object:nil];
#endif
    }
    return self;
}

queryCacheOperationForKey

经过一系列调用 ,我们最终会调用到

- (nullable SDImageCacheToken *)queryCacheOperationForKey:(nullable NSString *)key 
                                                 options:(SDImageCacheOptions)options 
                                                 context:(nullable SDWebImageContext *)context 
                                              cacheType:(SDImageCacheType)queryCacheType 
                                                   done:(nullable SDImageCacheQueryCompletionBlock)doneBlock {
    if (!key) {
        if (doneBlock) {
            doneBlock(nil, nil, SDImageCacheTypeNone);  // 键为空时直接返回无缓存
        }
        return nil;
    }
    // 无效缓存类型
    if (queryCacheType == SDImageCacheTypeNone) {
        if (doneBlock) {
            doneBlock(nil, nil, SDImageCacheTypeNone);  // 指定不查询任何缓存时直接返回
        }
        return nil;
    }
    
    // 首先检查内存缓存...
    UIImage *image;
    BOOL shouldQueryDiskOnly = (queryCacheType == SDImageCacheTypeDisk);  // 是否仅查询磁盘
    if (!shouldQueryDiskOnly) {
        image = [self imageFromMemoryCacheForKey:key];  // 从内存缓存获取图片
    }
    
    if (image) {
        // 处理动态图首帧静态化或类匹配逻辑
        if (options & SDImageCacheDecodeFirstFrameOnly) {
            // 确保静态图:若为动态图则提取首帧
            if (image.sd_imageFrameCount > 1) {
                image = [[UIImage alloc] initWithCGImage:image.CGImage ...];  // 生成静态图
            }
        } else if (options & SDImageCacheMatchAnimatedImageClass) {
            // 检查图片类是否匹配上下文要求(如仅允许WebP动画)
            Class desiredImageClass = context[SDWebImageContextAnimatedImageClass];
            if (desiredImageClass && ![image.class 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;
    
    // 判断是否需要同步查询磁盘
    BOOL shouldQueryDiskSync = ((image && options & SDImageCacheQueryMemoryDataSync) ||
                                (!image && options & SDImageCacheQueryDiskDataSync));
    
    // 定义磁盘数据查询块
    NSData* (^queryDiskDataBlock)(void) = ^NSData* {
        return [self diskImageDataBySearchingAllPathsForKey:key];  // 搜索所有磁盘路径获取数据
    };
    
    // 定义磁盘图片解码块
    UIImage* (^queryDiskImageBlock)(NSData*) = ^UIImage*(NSData* diskData) {
        UIImage *diskImage;
        if (image) {
            diskImage = image;  // 内存已命中但需原始数据
        } else if (diskData) {
            // 内存未命中,需解码磁盘数据
            diskImage = [self diskImageForKey:key data:diskData options:options context:context];
            if (shouldCacheToMemory) {
                [self _syncDiskToMemoryWithImage:diskImage forKey:key];  // 同步到内存缓存
            }
        }
        return diskImage;
    };
    
    // 执行磁盘查询(同步或异步)
    if (shouldQueryDiskSync) {
        // 同步查询(阻塞当前线程)
        dispatch_sync(self.ioQueue, ^{
            NSData *diskData = queryDiskDataBlock();
            UIImage *diskImage = queryDiskImageBlock(diskData);
            doneBlock(diskImage, diskData, SDImageCacheTypeDisk);
        });
    } else {
        // 异步查询(避免阻塞主线程)
        dispatch_async(self.ioQueue, ^{
            NSData *diskData = queryDiskDataBlock();
            UIImage *diskImage = queryDiskImageBlock(diskData);
            // 异步回调到主队列
            [(queue ?: SDCallbackQueue.mainQueue) async:^{
                doneBlock(diskImage, diskData, SDImageCacheTypeDisk);
            }];
        });
    }
    
    return operation;  // 返回可取消的操作对象
}

imageFromMemoryCacheForKey先使用该方法查看缓存,再通过diskImageDataBySearchingAllPathsForKey查看缓存

diskImageDataBySearchingAllPathsForKey: 方法是 SDWebImage 磁盘缓存系统的核心检索逻辑,用于通过指定 key 在多个潜在路径中搜索并加载缓存的图片数据。其核心逻辑如下:

参数校验

if (!key) {
    return nil;
}
  • 若 key 为空(如 URL 无效或未生成缓存键),直接返回空值,避免无效操作。

默认磁盘路径检索

NSData *data = [self.diskCache dataForKey:key];
if (data) {
    return data;
}
  • 优先从默认缓存路径(如沙盒的 Library/Caches/default/com.hackemist.SDImageCache)加载数据;
  • 使用 dataForKey: 方法直接匹配带扩展名的文件名(如 md5_hash.jpg),符合 SDWebImage 5.0+ 的缓存命名规则。

扩展路径兼容性检索

if (self.additionalCachePathBlock) {
    NSString *filePath = self.additionalCachePathBlock(key);
    if (filePath) {
        data = [NSData dataWithContentsOfFile:filePath options:self.config.diskCacheReadingOptions error:nil];
    }
}
  • 通过回调块 additionalCachePathBlock 支持自定义搜索路径,例如:
    • 预加载的本地资源目录(如 Bundle 中的图片);
    • 旧版本遗留的缓存路径(早期版本可能未包含扩展名);

图像下载

callDownloadProcessForOperation

无论我们在缓存之中找没找到对应图影片,我们都会进入下一步callDownloadProcessForOperation之中

// 核心下载流程处理方法(SDWebImageManager内部逻辑)
// 参数说明:
// - operation: 组合操作对象,用于管理缓存和下载的取消
// - url: 目标图片URL
// - options: 下载选项(如SDWebImageRetryFailed等)
// - context: 上下文配置字典,可传递自定义参数
// - cachedImage: 已存在的缓存图片(可能为nil)
// - cachedData: 已存在的缓存二进制数据(可能为nil)
// - cacheType: 缓存类型(内存/磁盘)
// - progressBlock: 下载进度回调
// - completedBlock: 最终完成回调
- (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; // 清空缓存操作引用
    }
    
    // 获取图片加载器(支持自定义加载器)
    id<SDImageLoader> imageLoader = context[SDWebImageContextImageLoader];
    if (!imageLoader) {
        imageLoader = self.imageLoader; // 使用默认加载器
    }
    
    // 判断是否需要下载网络图片的三重条件:
    // 1. 未设置仅从缓存加载选项(SDWebImageFromCacheOnly)
    // 2. 无缓存图片 或 设置了强制刷新选项(SDWebImageRefreshCached)
    // 3. 代理方法允许下载该URL
    BOOL shouldDownload = !SD_OPTIONS_CONTAINS(options, SDWebImageFromCacheOnly);
    shouldDownload &= (!cachedImage || options & SDWebImageRefreshCached);
    shouldDownload &= (![self.delegate respondsToSelector:@selector(imageManager:shouldDownloadImageForURL:)] || [self.delegate imageManager:self shouldDownloadImageForURL:url]);
    
    // 检查加载器是否支持该URL请求
    if ([imageLoader respondsToSelector:@selector(canRequestImageForURL:options:context:)]) {
        shouldDownload &= [imageLoader canRequestImageForURL:url options:options context:context];
    } else {
        shouldDownload &= [imageLoader canRequestImageForURL:url];
    }

    if (shouldDownload) {
        // 处理缓存刷新逻辑(当存在缓存但需要刷新时)
        if (cachedImage && options & SDWebImageRefreshCached) {
            // 先返回缓存图片,当启用 SDWebImageRefreshCached 选项时,即使存在缓存也会强制向服务器验证文件是否更新
            [self callCompletionBlockForOperation:operation completion:completedBlock 
                                            image:cachedImage 
                                            data:cachedData 
                                            error:nil 
                                       cacheType:cacheType 
                                         finished:YES 
                                           queue:context[SDWebImageContextCallbackQueue] 
                                             url:url];
            
            // 将缓存图片传递给加载器(用于304 Not Modified判断)
            SDWebImageMutableContext *mutableContext = [context mutableCopy] ?: [NSMutableDictionary dictionary];
            mutableContext[SDWebImageContextLoaderCachedImage] = cachedImage;
            context = [mutableContext copy];
        }
        
        // 弱引用operation避免循环引用
        @weakify(operation);
        // 启动图片加载器请求
        operation.loaderOperation = [imageLoader requestImageWithURL:url 
                                                             options:options 
                                                             context:context 
                                                            progress:progressBlock 
                                                           completed:^(UIImage *downloadedImage, NSData *downloadedData, NSError *error, BOOL finished) {
            @strongify(operation);
            if (!operation || operation.isCancelled) {
                // 用户取消操作时的错误处理
                [self callCompletionBlockForOperation:operation completion:completedBlock 
                                                error:[NSError errorWithDomain:SDWebImageErrorDomain 
                                                                          code:SDWebImageErrorCancelled 
                                                                      userInfo:@{NSLocalizedDescriptionKey : @"用户取消了操作"}] 
                                               queue:context[SDWebImageContextCallbackQueue] 
                                                 url:url];
            } else if (cachedImage && options & SDWebImageRefreshCached && [error.domain isEqualToString:SDWebImageErrorDomain] && error.code == SDWebImageErrorCacheNotModified) {
                // 服务器返回304 Not Modified时的特殊处理(不触发回调)[2](@ref)
            } 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]; // 加入失败URL列表
                    SD_UNLOCK(self->_failedURLsLock);
                }
            } else {
                // 成功下载处理
                if ((options & SDWebImageRetryFailed)) {
                    // 从失败列表中移除该URL
                    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];
    }
}
downloadImageWithURL
// SDWebImage下载核心方法:创建并管理图片下载任务
// 参数说明:
// - url: 图片请求URL(可为空)
// - options: 下载选项(如SDWebImageDownloaderHighPriority)
// - context: 上下文字典,可传递自定义参数(如解码选项、缓存策略)
// - progressBlock: 下载进度回调
// - completedBlock: 下载完成回调
// 返回值:SDWebImageDownloadToken对象,用于取消下载任务
- (nullable SDWebImageDownloadToken *)downloadImageWithURL:(nullable NSURL *)url
                                                   options:(SDWebImageDownloaderOptions)options
                                                   context:(nullable SDWebImageContext *)context
                                                  progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
                                                 completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock {
    // 空URL校验:立即触发错误回调
    if (url == nil) {
        if (completedBlock) {
            NSError *error = [NSError errorWithDomain:SDWebImageErrorDomain 
                                                 code:SDWebImageErrorInvalidURL 
                                             userInfo:@{NSLocalizedDescriptionKey : @"图片URL为空"}];
            completedBlock(nil, nil, error, YES); // finished标记为YES
        }
        return nil;
    }
    
    id downloadOperationCancelToken;
    // 生成缓存键:支持自定义缓存键过滤器(用于不同尺寸缩略图场景)
    id<SDWebImageCacheKeyFilter> cacheKeyFilter = context[SDWebImageContextCacheKeyFilter];
    NSString *cacheKey = cacheKeyFilter ? [cacheKeyFilter cacheKeyForURL:url] : url.absoluteString;
    
    // 获取解码选项:结合上下文与下载选项生成最终解码配置
    SDImageCoderOptions *decodeOptions = SDGetDecodeOptionsFromContext(context, [self.class imageOptionsFromDownloaderOptions:options], cacheKey);
    
    // 线程安全操作:使用互斥锁保护URLOperations字典
    SD_LOCK(_operationsLock);
    
    // 检查现有下载操作:通过URL查找是否已有运行中的任务
    NSOperation<SDWebImageDownloaderOperation> *operation = self.URLOperations[url];
    BOOL shouldNotReuseOperation = NO;
    
    // 判断操作是否可重用:已完成的或已取消的操作需要重建[6](@ref)
    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 : @"下载操作创建失败"}];
                completedBlock(nil, nil, error, YES);
            }
            return nil;
        }
        
        // 弱引用self避免循环引用
        __weak typeof(self) weakSelf = self;
        operation.completionBlock = ^{
            __strong typeof(weakSelf) strongSelf = weakSelf;
            if (!strongSelf) return;
            
            // 操作完成后从字典移除(线程安全)
            SD_LOCK(strongSelf->_operationsLock);
            [strongSelf.URLOperations removeObjectForKey:url];
            SD_UNLOCK(strongSelf->_operationsLock);
        };
        
        // 存储新操作到字典并加入队列
        self.URLOperations[url] = operation;
        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);
    
    // 生成下载令牌:封装操作与取消令牌
    SDWebImageDownloadToken *token = [[SDWebImageDownloadToken alloc] initWithDownloadOperation:operation];
    token.url = url;
    token.request = operation.request;
    token.downloadOperationCancelToken = downloadOperationCancelToken; // 用于取消特定回调
    
    return token;
}

我们发现,在这个方法之中似乎没有实现,网络下载的执行逻辑被封装在 SDWebImageDownloaderOperation 内部,通过start方法进行下载任务

start
// SDWebImageDownloaderOperation的核心启动方法
// 功能:初始化网络请求、管理任务生命周期、处理后台任务和线程安全
- (void)start {
    @synchronized (self) {  // 线程安全锁,防止多线程竞争[1,5](@ref)
        // 检查操作是否已被取消
        if (self.isCancelled) {
            if (!self.isFinished) self.finished = YES;
            // 触发取消错误回调(用户主动取消场景)
            [self callCompletionBlocksWithError:[NSError errorWithDomain:SDWebImageErrorDomain 
                                                                   code:SDWebImageErrorCancelled 
                                                               userInfo:@{NSLocalizedDescriptionKey : @"用户取消下载操作"}]];
            [self reset];  // 重置内部状态
            return;
        }

#if SD_UIKIT
        // 处理应用进入后台时的任务延续逻辑
        Class UIApplicationClass = NSClassFromString(@"UIApplication");
        BOOL hasApplication = UIApplicationClass && [UIApplicationClass respondsToSelector:@selector(sharedApplication)];
        if (hasApplication && [self shouldContinueWhenAppEntersBackground]) {
            __weak typeof(self) wself = self;
            UIApplication *app = [UIApplicationClass performSelector:@selector(sharedApplication)];
            // 开启后台任务防止应用挂起(iOS后台下载支持)
            self.backgroundTaskId = [app beginBackgroundTaskWithExpirationHandler:^{
                [wself cancel];  // 后台时间耗尽时自动取消任务
            }];
        }
#endif
        
        // 配置NSURLSession(无可用session时创建新session)
        NSURLSession *session = self.unownedSession;
        if (!session) {
            NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration];
            sessionConfig.timeoutIntervalForRequest = 15;  // 设置15秒请求超时
            
            /* 创建串行队列的session,保证代理方法顺序执行
             * delegateQueue设为nil时,系统自动创建串行队列
             */
            session = [NSURLSession sessionWithConfiguration:sessionConfig
                                                    delegate:self
                                               delegateQueue:nil];
            self.ownedSession = session;  // 持有session所有权[6](@ref)
        }

        // 处理忽略缓存响应选项(SDWebImageDownloaderIgnoreCachedResponse)
        if (self.options & SDWebImageDownloaderIgnoreCachedResponse) {
            NSURLCache *URLCache = session.configuration.URLCache ?: [NSURLCache sharedURLCache];
            NSCachedURLResponse *cachedResponse;
            @synchronized (URLCache) {  // NSURLCache线程安全访问[2](@ref)
                cachedResponse = [URLCache cachedResponseForRequest:self.request];
            }
            if (cachedResponse) {
                self.cachedData = cachedResponse.data;     // 存储缓存数据
                self.response = cachedResponse.response;   // 存储缓存响应头
            }
        }

        // 异常检查:session代理不可用
        if (!session.delegate) {
            [self callCompletionBlocksWithError:[NSError errorWithDomain:SDWebImageErrorDomain 
                                                                   code:SDWebImageErrorInvalidDownloadOperation 
                                                               userInfo:@{NSLocalizedDescriptionKey : @"Session代理失效"}]];
            [self reset];
            return;
        }
        
        // 创建数据任务并启动
        self.dataTask = [session dataTaskWithRequest:self.request];
        self.executing = YES;  // 标记操作进入执行状态[5](@ref)
    }  // 结束@synchronized块

    // 配置任务优先级并启动
    if (self.dataTask) {
        // 设置任务优先级(高/低/默认)
        if (self.options & SDWebImageDownloaderHighPriority) {
            self.dataTask.priority = NSURLSessionTaskPriorityHigh;
        } else if (self.options & SDWebImageDownloaderLowPriority) {
            self.dataTask.priority = NSURLSessionTaskPriorityLow;
        } else {
            self.dataTask.priority = NSURLSessionTaskPriorityDefault;
        }
        [self.dataTask resume];  // 启动网络请求[1](@ref)
        
        // 触发初始进度回调(0%进度)
        NSArray<SDWebImageDownloaderOperationToken *> *tokens;
        @synchronized (self) {
            tokens = [self.callbackTokens copy];
        }
        for (SDWebImageDownloaderOperationToken *token in tokens) {
            if (token.progressBlock) {
                token.progressBlock(0, NSURLResponseUnknownLength, self.request.URL);
            }
        }
        
        // 发送下载开始通知(主线程异步)
        __block typeof(self) strongSelf = self;
        dispatch_async(dispatch_get_main_queue(), ^{
            [[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStartNotification 
                                                                object:strongSelf];
        });
    } else {
        // 任务创建失败处理
        if (!self.isFinished) self.finished = YES;
        [self callCompletionBlocksWithError:[NSError errorWithDomain:SDWebImageErrorDomain 
                                                               code:SDWebImageErrorInvalidDownloadOperation 
                                                           userInfo:@{NSLocalizedDescriptionKey : @"任务初始化失败"}]];
        [self reset];
    }
}

总结流程

官方SDWebImage的流程图


网站公告

今日签到

点亮在社区的每一天
去签到