由浅入深,从基本概念到源码解析,带你全面掌握图片加载框架的设计与实现
一、什么是 SDWebImage? 1.1 为什么需要图片加载库? 在 iOS 开发中,展示网络图片是极其常见的需求。如果自己实现,需要处理:
异步下载 :不能阻塞主线程
缓存策略 :内存缓存 + 磁盘缓存,避免重复下载
图片解码 :在主线程解码大图会导致卡顿
复用与取消 :列表滑动时,旧请求应及时取消,避免错乱
格式支持 :JPEG、PNG、GIF、WebP 等
SDWebImage 将这些能力封装成一套成熟方案,被广泛应用于 App 中。
1.2 SDWebImage 简介 SDWebImage 是一个异步图片下载与缓存库,支持 iOS、macOS、watchOS、visionOS。核心特性包括:
特性
说明
异步下载
基于 NSURLSession,不阻塞主线程
内存 + 磁盘缓存
支持自定义缓存策略、过期时间
后台解码
避免主线程解码导致的卡顿
渐进式加载
支持 JPEG 等格式的渐进显示
动图支持
GIF、APNG、WebP 动画
缩略图解码
大图可只解码指定尺寸,节省内存
协议化设计
v5.x 起核心组件可插拔、可替换
1.3 版本演进要点
版本
主要变化
4.x
Block 回调、FLAnimatedImageView
5.x
协议化:Loader、Cache、Coder 均可自定义;新增 View Indicator、Image Transform
5.6+
完善协议体系,架构更清晰
二、核心架构与原理 2.1 整体数据流 一次完整的图片加载流程大致如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 用户调用 sd_setImageWithURL: │ ▼ ┌───────────────────────────────────────┐ │ UIView+WebCache (便捷入口) │ │ sd_internalSetImageWithURL:... │ └───────────────┬───────────────────────┘ │ ▼ ┌───────────────────────────────────────┐ │ SDWebImageManager (调度中心) │ │ loadImageWithURL:options:... │ └───────────────┬───────────────────────┘ │ ┌───────────┼───────────┐ ▼ ▼ ▼ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ Cache │ │ Loader │ │ Coder │ │ 查找缓存 │ │ 下载图片 │ │ 解码图片 │ └─────────┘ └─────────┘ └─────────┘ │ ▼ 显示到 UIImageView
2.2 协议化设计(v5.x 核心) v5.x 将核心能力抽象为协议,实现可替换、可扩展:
协议
默认实现
职责
SDImageCache
SDImageCache
内存 + 磁盘缓存
SDImageLoader
SDWebImageDownloader
网络/本地图片加载
SDImageCoder
SDImageCodersManager
图片编解码
SDWebImageCacheSerializer
-
自定义缓存序列化
SDWebImageIndicator
SDWebImageActivityIndicator
加载状态指示器
这种设计让开发者可以:
替换默认下载器(如接入自研 CDN SDK)
使用自定义缓存(如接入 YYCache、PINCache)
支持新图片格式(实现 Coder 协议即可)
2.3 主线程检测的演进 SDWebImage 中使用 dispatch_main_async_safe 确保 UI 更新在主线程/主队列执行。v5.x 对主线程检测做了改进:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 #define dispatch_main_async_safe(block)\ if ([NSThread isMainThread]) { block(); } else {\ dispatch_async(dispatch_get_main_queue(), block);\ } #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);\ }
原因:主队列 ≠ 主线程 。某些场景下,非主队列的任务可能在主线程执行,若依赖 isMainThread,在依赖「主队列」的框架(如 VektorKit)中会出现问题。主队列上的任务一定在主线程执行,反之则需用队列标签判断。
三、源码解析 3.1 入口:UIView + WebCache 所有 View 的图片设置最终汇聚到 sd_internalSetImageWithURL:...:
1 2 3 4 5 6 7 - (void )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;
核心步骤(精简):
取消旧任务 :sd_cancelImageLoadOperationWithKey:,避免同一 View 的多次请求冲突
设置占位图 :立即显示 placeholder
调用 Manager :loadImageWithURL:options:context:progress:completed:
保存 Operation :将返回的 id<SDWebImageOperation> 存入 sd_operationDictionary,便于取消
sd_operationDictionary 使用 NSMapTable,key 强引用、value 弱引用,因为 Operation 由 Manager 的 runningOperations 持有,这里仅作取消用。
3.2 SDWebImageManager:调度中心 Manager 负责串联 Cache、Loader、Coder,核心逻辑在 loadImageWithURL:options:context:progress:completed::
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 NSString *key = [self cacheKeyForURL:url context:context];if (!(options & SDWebImageFromLoaderOnly)) { [self callQueryCacheOperationForKey:key ... completed:^(UIImage *cachedImage, NSData *cachedData, SDImageCacheType cacheType) { if (cachedImage) { completedBlock(cachedImage, cachedData, nil , cacheType, YES , url); return ; } [self callLoadOperationWithURL:url ... completed:^(UIImage *image, NSData *data, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) { if (image && (options & SDWebImageCacheMemoryOnly)) { [self .imageCache storeImage:image forKey:key ...]; } completedBlock(image, data, error, cacheType, finished, imageURL); }]; }]; }
流程:查缓存 → 未命中则下载 → 下载完成写缓存 → 回调 。
3.3 SDWebImageDownloader:下载器 下载器负责发起网络请求,支持并发数、超时、Request/Response 修改等配置:
1 2 3 4 5 6 - (nullable SDWebImageDownloadToken *)downloadImageWithURL:(nullable NSURL *)url options:(SDWebImageDownloaderOptions)options context:(nullable SDWebImageContext *)context progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock completed:(nullable SDWebImageDownloaderCompletedBlock)completedBlock;
URL 复用 :同一 URL 的多次请求会复用同一个 SDWebImageDownloaderOperation,通过 addHandlersForProgress:completed: 累积多个回调,下载完成后依次执行,避免重复下载。
1 2 3 4 5 6 7 8 9 10 NSOperation <SDWebImageDownloaderOperation> *operation = [self .URLOperations objectForKey:url];if (operation) { @synchronized (operation) { downloadOperationCancelToken = [operation addHandlersForProgress:progressBlock completed:completedBlock]; } } else { operation = [self createDownloaderOperationWithUrl:url options:options context:context]; [self .URLOperations setObject:operation forKey:url]; }
可插拔扩展 :
SDWebImageDownloaderRequestModifier:修改 Request(如加 Header)
SDWebImageDownloaderResponseModifier:修改 Response(如校验 MIME-Type)
SDWebImageDownloaderDecryptor:解密(如 Base64)
3.4 SDImageCache:缓存 缓存采用内存 + 磁盘 二层结构:
1 2 3 4 5 6 7 8 9 10 11 12 - (void )queryImageForKey:(NSString *)key options:(SDImageCacheOptions)options context:(nullable SDWebImageContext *)context completion:(nullable SDImageCacheQueryCompletionBlock)completionBlock; - (void )storeImage:(UIImage *)image imageData:(NSData *)imageData forKey:(NSString *)key cacheType:(SDImageCacheType)cacheType completion:(nullable SDWebImageNoParamsBlock)completionBlock;
内存缓存 :基于 NSCache,受内存压力和系统策略自动回收
磁盘缓存 :默认使用 NSFileManager 存储到 Library/Caches/default,可配置自定义路径
缓存 Key 默认为 URL 的绝对字符串,可通过 SDWebImageContext 的 cacheKeyFilter 自定义。
3.5 解码与变换 为避免主线程解码导致卡顿,SDWebImage 在后台队列 解码:
1 2 3 - (UIImage *)decodedImageWithData:(NSData *)data options:(SDImageCoderOptions *)options;
v5.x 支持 Image Transform :下载后可对图片做缩放、旋转、圆角等处理,结果再缓存,避免重复计算。
四、示例 4.1 基础用法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 #import <SDWebImage/SDWebImage.h> [self .imageView sd_setImageWithURL:[NSURL URLWithString:@"https://example.com/photo.jpg" ]]; [self .imageView sd_setImageWithURL:url placeholderImage:[UIImage imageNamed:@"placeholder" ] completed:^(UIImage * _Nullable image, NSError * _Nullable error, SDImageCacheType cacheType, NSURL * _Nullable imageURL) { if (error) { NSLog (@"加载失败: %@" , error); } else { NSLog (@"加载成功,来源: %@" , cacheType == SDImageCacheTypeMemory ? @"内存" : cacheType == SDImageCacheTypeDisk ? @"磁盘" : @"网络" ); } }];
4.2 进度与选项 1 2 3 4 5 6 7 [self .imageView sd_setImageWithURL:url placeholderImage:placeholder options:SDWebImageRetryFailed | SDWebImageProgressiveLoad progress:^(NSInteger receivedSize, NSInteger expectedSize, NSURL * _Nullable targetURL) { CGFloat progress = expectedSize > 0 ? (CGFloat )receivedSize / expectedSize : 0 ; self .progressView.progress = progress; } completed:nil ];
常用 options:
SDWebImageRetryFailed:失败后重试
SDWebImageProgressiveLoad:渐进式加载
SDWebImageRefreshCached:忽略缓存强制刷新
SDWebImageFromLoaderOnly:只从网络加载,不查缓存
4.3 预加载 1 2 3 4 5 6 7 SDWebImagePrefetcher *prefetcher = [SDWebImagePrefetcher sharedImagePrefetcher]; prefetcher.maxConcurrentPrefetches = 4 ; [prefetcher prefetchURLs:imageURLs progress:^(NSUInteger no, NSUInteger total) { NSLog (@"预加载进度: %lu/%lu" , (unsigned long )no, (unsigned long )total); } completed:^(NSUInteger finishedCount, NSUInteger skippedCount) { NSLog (@"完成: %lu, 跳过: %lu" , (unsigned long )finishedCount, (unsigned long )skippedCount); }];
4.4 自定义缓存 Key 1 2 3 4 5 6 SDWebImageContext *context = @{ SDWebImageContextCacheKeyFilter: [SDWebImageCacheKeyFilter cacheKeyFilterWithBlock:^NSString * _Nullable(NSURL * _Nullable url) { return [url.absoluteString stringByAppendingString:@"_suffix" ]; }] }; [self .imageView sd_setImageWithURL:url placeholderImage:nil context:context];
4.5 图片变换(圆角、缩放) 1 2 3 4 [self .imageView sd_setImageWithURL:url placeholderImage:placeholder options:0 context:@{SDWebImageContextImageTransformer: [SDImageRoundCornerTransformer transformerWithRadius:10 corners:UIRectCornerAllCorners borderWidth:1 borderColor:[UIColor whiteColor]]}];
五、实际项目中的应用案例 5.1 列表图片加载与复用 场景 :TableView/CollectionView 中加载头像或商品图。
做法 :
使用 sd_setImageWithURL: 即可,SDWebImage 会自动取消不可见 cell 的请求
可选 SDWebImageAvoidAutoSetImage,在 completed 中手动设置,便于加入过渡动画
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { MyCell *cell = [tableView dequeueReusableCellWithIdentifier:@"MyCell" ]; NSURL *avatarURL = self .avatars[indexPath.row]; [cell.avatarView sd_setImageWithURL:avatarURL placeholderImage:[UIImage imageNamed:@"default_avatar" ] options:SDWebImageAvoidAutoSetImage completed:^(UIImage * _Nullable image, NSError * _Nullable error, SDImageCacheType cacheType, NSURL * _Nullable imageURL) { if (image) { [UIView transitionWithView:cell.avatarView duration:0.2 options:UIViewAnimationOptionTransitionCrossDissolve animations:^{ cell.avatarView.image = image; } completion:nil ]; } }]; return cell; }
5.2 详情页大图预加载 场景 :从列表进入详情时,希望大图尽快展示。
做法 :在列表滑动到某条数据时,对详情大图 URL 做预加载:
1 2 3 4 5 6 - (void )tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { NSString *detailImageURL = self .detailImageURLs[indexPath.row]; [[SDWebImagePrefetcher sharedImagePrefetcher] prefetchURLs:@[[NSURL URLWithString:detailImageURL]]]; [self .navigationController pushViewController:detailVC animated:YES ]; }
5.3 统一添加请求头(如 Token) 场景 :图片 URL 需要鉴权 Header。
做法 :实现 SDWebImageDownloaderRequestModifier:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 @interface MyRequestModifier : NSObject <SDWebImageDownloaderRequestModifier >@end @implementation MyRequestModifier - (NSURLRequest *)modifiedRequestWithRequest:(NSURLRequest *)request { NSMutableURLRequest *mutable = [request mutableCopy]; [mutable setValue:[MyAuthManager shared].token forHTTPHeaderField:@"Authorization" ]; return [mutable copy ]; } @end SDWebImageDownloader *downloader = [SDWebImageManager sharedManager].imageLoader; downloader.requestModifier = [[MyRequestModifier alloc] init];
5.4 加载状态指示器 场景 :图片加载时显示 UIActivityIndicator。
1 2 self .imageView.sd_imageIndicator = SDWebImageActivityIndicator.grayIndicator;[self .imageView sd_setImageWithURL:url];
或自定义实现 SDWebImageIndicator 协议,适配项目 UI 规范。
5.5 自定义缓存路径与策略 场景 :头像与普通图片使用不同缓存目录和过期策略。
1 2 3 4 5 6 7 SDImageCache *avatarCache = [[SDImageCache alloc] initWithNamespace:@"avatar" diskCacheDirectory:[NSSearchPathForDirectoriesInDomains (NSCachesDirectory , NSUserDomainMask , YES ).firstObject stringByAppendingPathComponent:@"AvatarCache" ]]]; avatarCache.config.maxDiskAge = 30 * 24 * 60 * 60 ; SDWebImageContext *context = @{SDWebImageContextCustomCache: avatarCache}; [self .avatarView sd_setImageWithURL:avatarURL placeholderImage:nil context:context];
六、小结 SDWebImage 通过协议化设计 和清晰的职责划分 ,将图片加载、缓存、解码、展示等环节解耦,既易用又易扩展。掌握其架构与源码,有助于:
合理选用 options 与 context,优化体验与性能
在需要时自定义 Loader、Cache、Coder,适配业务
理解异步加载、缓存、取消等通用模式,迁移到其他场景
建议结合官方 GitHub 与 Wiki 阅读源码,逐层从 Category → Manager → Loader/Cache 跟踪调用链,会有更深理解。