SDWebImage源码学习_sdwebimagequerymemorydata
SDWebImage源码学习
文章目录
- SDWebImage源码学习
-
- 前言
- 宏定义
- SDWebImageOptions
- SDWebImageContext
- SDOperationsDictionary
- 入口函数
-
- 1. 操作唯一性与冲突避免
- 2. 线程安全与生命周期管理
- 3. 操作键(Operation Key)机制
- 4. 与 SDWebImage 核心模块协作
- 工具层
-
- 缓存查找
-
- SDImageCacheConfig
- SDImageCacheType
- processedResultForURL
- callCacheProcessForOperation
-
- cacheKeyForURL 和 originalCacheKeyForURL
- SDImageCache
-
- 初始化方法
- queryCacheOperationForKey
- 图像下载
-
-
- callDownloadProcessForOperation
- downloadImageWithURL
- start
-
- 总结流程
前言
SDWebImage的具体流程图
宏定义
#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
SDWebImageContextSetImageOperationKey
NSString
UIButton
不同状态)多次加载时的任务冲突。默认使用视图类名生成唯一键。SDWebImageContextImageCache
id
SDImageCache.sharedImageCache
。SDWebImageContextImageLoader
id
SDWebImageDownloader
。SDWebImageContextImageDecodeOptions
SDImageCoderOptions
SDImageCoderDecodeThumbnailPixelSize
)或渐进式解码参数,减少内存占用并提升渲染性能。SDWebImageContextCallbackQueue
SDCallbackQueue
SDWebImageContextStoreCacheType
SDImageCacheType
SDWebImageContextImageTransformer
id
SDImageTransformer
协议。SDWebImageContextTransition
SDWebImageTransition
SDWebImageContextDownloadRequestModifier
id
SDOperationsDictionary
typedef NSMapTable<NSString *, id<SDWebImageOperation>> SDOperationsDictionary;
SDOperationsDictionary
继承于NSMapTab,当视图发起新的图片加载请求时,SDOperationsDictionary
会通过唯一标识符(如 validOperationKey
)查找并取消当前视图关联的未完成操作,
NSCopying
的 Objective-C 对象入口函数
我们看到,无论如何调用哪一个函数,最终都会进入
- (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
)是用于 管理视图(如 UIImageView
、UIButton
)关联的图片加载操作的核心数据结构。它的作用主要体现在以下几个方面:
1. 操作唯一性与冲突避免
-
取消旧任务:当视图发起新的图片加载请求时,
operationDictionary
会通过唯一标识符(如validOperationKey
)查找并取消当前视图关联的未完成操作。例如:[self sd_cancelImageLoadOperationWithKey:validOperationKey];
这确保同一视图不会因重复请求(如快速滑动列表时)导致图片错位或资源浪费。
-
多操作共存:支持不同操作键区分同一视图的多个独立任务(如同时加载头像和背景图)。
2. 线程安全与生命周期管理
- 线程安全存储:通过关联对象(Associated Object)动态挂载到
UIView
上,使用NSMapTable
或NSMutableDictionary
存储操作,确保多线程环境下操作增删的安全性。 - 自动释放机制:当视图释放时,关联的
operationDictionary
会自动清理未完成的操作,避免内存泄漏。
3. 操作键(Operation Key)机制
- 灵活标识符:默认以视图类名(如
UIImageView
)作为操作键,但也支持通过上下文(SDWebImageContext
)自定义键,适应复杂场景。 - 动态绑定:例如,同一
UIImageView
的不同加载任务(如普通状态与高亮状态)可通过不同键独立管理。
4. 与 SDWebImage 核心模块协作
- 协调下载与缓存:通过
operationDictionary
管理SDWebImageOperation
对象(如下载任务SDWebImageDownloaderOperation
),实现与SDWebImageManager
和SDImageCache
的交互。 - 支持优先级控制:例如,在
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 的缓存流程中,cacheKeyForURL
和 originalCacheKeyForURL
生成的缓存键有以下核心区别:
cacheKeyForURL
用于生成 转换后的缓存键,通常包含图片的 处理参数(如缩略图尺寸、裁剪模式、压缩质量等)。例如,若 URL 中包含thumbnail=300x300
或quality=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 框架中 专门负责图片缓存管理的核心组件,其核心功能与设计模式如下:
-
自动缓存管理
- 过期策略:默认清理超过一周(
maxCacheAge
默认 7 天)的缓存文件。 - 容量控制:可设置最大缓存大小(
maxCacheSize
),超出时按 LRU(最近最少使用)策略清理。 - 后台清理:通过监听
UIApplicationDidEnterBackgroundNotification
等系统事件,在后台线程异步清理磁盘缓存。
- 过期策略:默认清理超过一周(
-
线程安全与性能优化
- 使用
dispatch_semaphore
控制对NSMapTable
弱引用缓存的线程安全访问。 - 图片解码优化:通过
SDWebImageDecoder
提前解压图片数据,避免主线程重复解码造成的性能损耗。
- 使用
-
默认单例模式
-
全局访问入口:通过
+[SDImageCache sharedImageCache]
获取单例实例,使用dispatch_once
确保线程安全初始化。 -
统一资源管理:单例模式便于集中控制内存和磁盘缓存,避免多实例导致的资源重复或状态不一致。
-
-
支持多实例场景
-
命名空间隔离:可通过
initWithNamespace:diskCacheDirectory:
创建独立命名空间的缓存实例,实现不同内容的缓存隔离(如用户头像与商品图片分开存储)。 -
灵活配置:每个实例可单独设置
maxCacheAge
、maxCacheSize
等参数,适应不同缓存策略需求。
-
初始化方法
看一下初始化方法,理解
- (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:YESqueue: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:downloadedDatacacheType:SDImageCacheTypeNone finished:finishedcompleted: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]; }}