📋 目录
- 一、SDWebImage 概述与历史演进
- 二、图像解码管线(Decoder Pipeline)
- 三、图像变换管线(Transformer Pipeline)
- 四、类结构图分析
- 五、与系统及业界实践的衔接
- 六、使用案例与原理分析
- 七、设计模式与编程思想
- 八、使用示例与最佳实践
- 九、常见面试题
- 参考文献
一、SDWebImage 概述与历史演进
0. 框架结构概览与功能简介
SDWebImage 的框架结构
SDWebImage 的图片下载分类,只要一行代码就可以实现图片异步下载和缓存功能。
功能简介
- 一个添加了 web 图片加载和缓存管理的 UIImageView 分类
- 一个异步图片下载器
- 一个异步的内存加磁盘综合存储图片并且自动处理过期图片
- 支持动态 gif 图
- 4.0 之前的动图效果并不是太好
- 4.0 以后基于 FLAnimatedImage 加载动图
- 支持 webP 格式的图片
- 后台图片解压处理
- 确保同样的图片 url 不会下载多次
- 确保伪造的图片 url 不会重复尝试下载
- 确保主线程不会阻塞
1. 框架简介
SDWebImage 是 Apple 平台(iOS / macOS / watchOS / visionOS)上广泛使用的异步图片下载与缓存库,提供从网络(或自定义 Loader)加载图片、解码、变换、缓存到展示的完整管线。其「图层处理」相关能力主要体现在:解码管线(将压缩数据解码为可渲染的位图)与变换管线(在解码后对位图做缩放、裁剪、滤镜等处理),二者共同构成「从数据到屏幕」的中间处理层。
2. 技术演进与版本脉络
SDWebImage 的图层处理能力并非一蹴而就,而是随版本逐步完善,与系统 API 和业界实践同步演进。
| 阶段 | 版本/时期 | 解码与图层处理相关能力 |
|---|---|---|
| 早期 | 3.x 及以前 | 以网络下载 + 简单缓存为主,解码依赖系统默认行为 |
| 规范化 | 4.0 | 引入 Custom Download Operation、更清晰的职责划分 |
| 编解码扩展 | 4.2 | Custom Coder:支持注册自定义编解码器(如 WebP、渐进 JPEG) |
| 统一管线 | 5.0 | Image Transformer、Animated Image 全栈方案(GIF/WebP/APNG)、解码与变换在 Manager 内统一调度 |
| 精细化 | 5.x 后续 | 缩略图解码、HDR、强制解码策略(Force Decode Policy)、解码尺度与字节限制等 |
5.0 是重要分水岭:解码(Coder)、变换(Transformer)、缓存(Cache)、加载(Loader)在 SDWebImageManager 中形成一条清晰流水线,便于理解「图层处理」在整体中的位置。
3. 图层处理在整体架构中的位置
下图概括了从「URL 请求」到「显示到视图」的流程,并标出解码与变换所在阶段。
flowchart LR
subgraph 输入
A[URL / 自定义 Loader]
end
subgraph 加载
B[SDImageLoader]
end
subgraph 解码层
C[Data]
D[SDImageCoder 解码]
E[UIImage/NSImage]
end
subgraph 变换层
F[SDImageTransformer]
G[变换后图像]
end
subgraph 缓存与输出
H[SDImageCache]
I[UIImageView 等]
end
A --> B --> C --> D --> E --> F --> G --> H --> I
要点:
- 解码(Decoder):将压缩格式数据(JPEG/PNG/WebP/HEIC/AVIF 等)转为内存中的位图(如
UIImage/NSImage),是「数据 → 图层」的第一步。 - 变换(Transformer):在「已解码的位图」上做几何或像素级处理(缩放、裁剪、圆角、滤镜等),输出仍是位图,再写入缓存或交给视图。
- 二者均可在后台线程执行,避免阻塞主线程,符合 Apple 在 WWDC 等场合强调的「Image and Graphics Best Practices」[1]。
二、图像解码管线(Decoder Pipeline)
1. 解码的基础概念与双缓冲模型
在操作系统与图形栈中,图像通常以两种形式存在:
-
数据缓冲(Data Buffer)
即磁盘或网络中的压缩编码数据(如 JPEG、PNG 的二进制)。体积小,但不能直接用于渲染。 -
图像缓冲(Image Buffer)
解码后的像素矩阵(如 RGBA 位图),可被 GPU/CPU 渲染。其大小与分辨率(宽×高×通道数)成正比,与压缩格式无关。
因此,解码(Decoding) 的含义是:将 Data Buffer 转换为 Image Buffer。该过程是 CPU 密集型,且解码后的图像缓冲往往远大于原始数据(例如一张 4K 图片可解码为上百 MB 像素数据)。系统会在首次渲染时触发解码,若在主线程进行,易造成卡顿;若不经控制,大图会带来内存峰值与 OOM 风险。
双缓冲概念可归纳为:
┌─────────────────┐ decode ┌─────────────────┐
│ Data Buffer │ ────────────► │ Image Buffer │
│ (JPEG/PNG/…) │ (CPU 密集) │ (像素矩阵) │
└─────────────────┘ └─────────────────┘
体积较小 体积 ∝ 宽×高×4
Apple 在 WWDC 2018「Image and Graphics Best Practices」[1] 中明确指出:解码后的缓冲区大小由图像尺寸决定,而非显示尺寸;因此在解码阶段就做下采样(Downsampling),避免先解码全尺寸再缩放的巨大内存与 CPU 开销。
2. 缩略图与下采样(Downsampling)
下采样指在解码时直接生成较小尺寸的位图,而不是先解码全图再缩放。这样既能减少内存占用,也能减少解码与后续绘制的计算量。
2.1 系统 API:ImageIO 与缩略图
在 iOS/macOS 上,推荐使用 ImageIO 的 CGImageSourceCreateThumbnailAtIndex 在解码阶段就限制最大尺寸,从而在内存中只生成缩略图级别的像素缓冲 [2][3]。
算法思路(伪代码):
函数 DownsampleImage(数据 data, 最大边长 maxPixelSize):
1. 使用 data 创建 CGImageSourceRef source
2. 设置选项 options:
- kCGImageSourceCreateThumbnailFromImageAlways: true
- kCGImageSourceCreateThumbnailWithTransform: true
- kCGImageSourceThumbnailMaxPixelSize: maxPixelSize
3. thumbnail = CGImageSourceCreateThumbnailAtIndex(source, 0, options)
4. 由 thumbnail 创建 UIImage/NSImage 并返回
这样,解码器内部可以在部分解码或低分辨率解码路径上生成缩略图,避免全图解码。SDWebImage 在 5.x 中通过 SDImageCoderDecodeScaleDownLimitBytes 等能力,将「按目标尺寸或字节限制做缩略图解码」纳入其解码管线,与上述思路一致。
2.2 下采样算法与内存估算
算法步骤(与 ImageIO 语义一致):
- 由
Data创建CGImageSourceRef,不立即解码全图。 - 从 source 读取图像属性(宽、高),计算缩放比例,使长边不超过
maxPixelSize。 - 设置
kCGImageSourceThumbnailMaxPixelSize、kCGImageSourceCreateThumbnailFromImageAlways、kCGImageSourceCreateThumbnailWithTransform等选项。 - 调用
CGImageSourceCreateThumbnailAtIndex(source, 0, options)得到缩略图CGImage。 - 由
CGImage创建UIImage/NSImage并返回。
这样解码器在内部只生成目标尺寸的像素缓冲,避免「先全图解码再缩放」的双倍内存与 CPU 开销。
2.3 内存与性能关系
下采样带来的内存节省可近似表示为:
- 全图解码:
memory ≈ width × height × 4(假设 RGBA)。 - 限制最大边长
L后:若等比缩放,则memory ≈ L² × 4,与原始分辨率无关。
因此,在列表、缩略图等场景下,在解码阶段就限制最大尺寸是业界公认的最佳实践,也是 SDWebImage 解码管线优化的核心之一。
3. 渐进式解码(Progressive Decoding)
渐进式编码(如 Progressive JPEG)允许数据分块到达时逐步呈现:先看到模糊全图,再随数据增多逐步变清晰。渐进式解码即在未完整接收数据时,对当前已有数据做解码并显示,以提升感知性能(尤其在弱网环境)[4]。
流程概念:
sequenceDiagram
participant N as 网络
participant D as 渐进式解码器
participant V as 视图
N->>D: 数据块 1
D->>D: 解码当前数据
D->>V: 显示低分辨率帧 1
N->>D: 数据块 2
D->>D: 更新解码状态,解码
D->>V: 显示更清晰帧 2
N->>D: 数据块 n(完成)
D->>D: 最终解码
D->>V: 显示最终图像
SDWebImage 通过 SDWebImageProgressiveCoder 协议扩展解码器:支持「增量数据」输入,每次 updateIncrementalData:finished: 时更新内部解码状态并输出当前可用的图像,供上层展示。对动图(如 GIF),还可配合 SDAnimatedImageCoder 在渐进加载时逐帧解码并驱动 SDAnimatedImageView 的渐进动画。
渐进式解码流程(伪代码):
函数 ProgressiveDecode:
状态: 已接收数据 buffer, 解码器内部状态 decoderState
当 收到新数据块 chunk:
append(buffer, chunk)
decodedFrame = decoder.decodeIncremental(buffer, decoderState)
若 decodedFrame 非空:
回调 onPartialImage(decodedFrame)
当 数据接收完成:
finalImage = decoder.finalize(buffer, decoderState)
回调 onComplete(finalImage)
注意:渐进式解码比单次完整解码的 CPU 开销更高 [4],适合「先显示再细化」的体验需求,需在流畅度与电量之间做权衡。
4. 编解码器扩展与多格式支持
SDWebImage 将「解码 / 编码」抽象为 SDImageCoder 协议,通过 SDImageCodersManager 注册多个 Coder,按数据格式(或 MIME 类型)选择对应实现。这样可在不修改核心管线的前提下支持新格式。
解码器选择与解码流程(高层):
flowchart TD
A[Image Data] --> B{SDImageCodersManager}
B --> C[遍历已注册 Coder]
C --> D{canDecodeFromData?}
D -->|是| E[该 Coder 解码]
D -->|否| C
E --> F[UIImage/NSImage]
F --> G[可选: 缩略图/字节限制]
G --> H[解码结果]
典型 Coder 职责:
| 方法/能力 | 含义 |
|---|---|
decodedImageWithData:options: | 将 Data 解码为 UIImage/NSImage |
encodedDataWithImage:format:options: | 将图像编码为指定格式 Data |
canDecodeFromData: / canEncodeToFormat: | 是否支持某格式的解码/编码 |
动图则通过 SDAnimatedImageCoder 扩展:提供按帧解码、帧时长、循环次数等,供 SDAnimatedImage + SDAnimatedImageView 使用。内置支持 GIF、WebP、APNG、HEIC 动图等;用户也可实现自定义 Coder 并注册,从而纳入统一的加载与缓存流程。
三、图像变换管线(Transformer Pipeline)
1. 变换器的设计思想与协议
图像变换在 SDWebImage 中定义为:输入与输出均为图像对象(如 UIImage/NSImage)的运算。与 Coder(Data ↔ Image)不同,Transformer 只做 Image → Image,例如缩放、裁剪、圆角、滤镜等,对应「数字图像处理」中的几何变换与像素操作 [5]。
协议设计(概念):
协议 SDImageTransformer:
方法 transform(image, key) -> Image?:
输入: 原始图像、缓存 key(可选,用于生成变换后的 cache key)
输出: 变换后的图像;失败可返回 nil
这样设计便于:
- 在 SDWebImageManager 中,在「解码完成」之后、「写入缓存」之前插入变换步骤;
- 对同一 URL 可因不同变换参数得到不同 cache key,从而分别缓存原图与变换结果。
2. 内置变换器与组合管线
SDWebImage 提供多种内置 Transformer,覆盖常见 UI 需求:
| 变换器 | 功能说明 |
|---|---|
| SDImageResizingTransformer | 缩放到指定尺寸,支持 scaleMode(fill/aspectFit 等) |
| SDImageCroppingTransformer | 按矩形裁剪 |
| SDImageRoundCornerTransformer | 圆角(可带边框) |
| SDImageRotationTransformer | 按角度旋转,可选 fitSize |
| SDImageFlippingTransformer | 水平/垂直翻转 |
| SDImageBlurTransformer | 高斯模糊 |
| SDImageTintTransformer | 颜色 tint |
| SDImageFilterTransformer | 基于 CIFilter 的滤镜(除 watchOS 外) |
组合管线:通过 SDImagePipelineTransformer 将多个 Transformer 按顺序组合,形成链式处理:
图像 → Transformer1 → Transformer2 → … → TransformerN → 最终图像
例如先裁剪再圆角再缩放,只需将三个 Transformer 放入一个 Pipeline 即可。对应伪代码:
pipeline = SDImagePipelineTransformer([CropTransformer(rect), RoundCornerTransformer(radius), ResizingTransformer(size)])
resultImage = pipeline.transform(originalImage, key)
在 Swift/Objective-C 中的用法可参见官方 Advanced Usage - Image Transformer [6]。
3. 变换与缓存的协同
变换发生在 Manager 层:
先由 Loader 得到 Data,由 Coder 解码得到 Image,再经 Transformer 得到最终 Image,最后再写入 Cache 并交给 UI。因此:
- 原始图与变换后的图可以分别缓存:
- 原图可用 SDWebImageContextOriginalImageCache 指定单独缓存实例;
- 变换后的图使用默认(或指定)的 Cache,其 cache key 会包含变换信息,避免不同变换结果互相覆盖。
- 若只关心「下载 + 变换」而不写缓存,可通过
.fromLoaderOnly且storeCacheType = .none实现,仅走 Loader → 解码 → 变换 → 回调,不读/写缓存。
变换与缓存的整体管线(含解码):
flowchart LR
subgraph 请求
U[URL + Context]
end
subgraph 缓存查询
C1{查 Cache}
end
subgraph 加载与解码
L[Loader]
D[Coder 解码]
end
subgraph 变换
T[Transformer]
end
subgraph 写回与展示
C2[写 Cache]
V[View]
end
U --> C1
C1 -->|命中| V
C1 -->|未命中| L --> D --> T --> C2 --> V
4. 应用场景简述
| 场景 | 解码侧 | 变换侧 |
|---|---|---|
| 列表缩略图 | 使用 scaleDown/limitBytes 做缩略图解码,降低内存 | 可选 ResizingTransformer 统一尺寸 |
| 头像/圆角 | 常规解码即可 | RoundCornerTransformer |
| 弱网/大图 | Progressive Coder 渐进显示 | 可配合 Resizing 限制最终尺寸 |
| 相册/大图预览 | 原图或高分辨率解码 | 少用或仅做旋转/裁剪 |
| 动图(GIF/WebP) | SDAnimatedImageCoder + 帧缓冲 | 一般不做几何变换,或仅对首帧做 |
四、类结构图分析
1. 核心类总览
SDWebImage 的类可按职责分为:入口与协调、加载、缓存、解码、变换、视图扩展 六类。下表给出核心类及其职责(名称以 5.x 为主,OC/Swift 可能略有差异)。
| 模块 | 核心类 / 协议 | 职责简述 |
|---|---|---|
| 协调 | SDWebImageManager | 统一入口:协调 Loader、Cache、Coder、Transformer,执行「查缓存 → 下载 → 解码 → 变换 → 写缓存」 |
| 加载 | SDImageLoader (协议) | 定义加载接口:根据 URL 返回 Data 或 Image |
SDWebImageDownloader | 默认 Loader 实现:基于 URLSession 下载,支持并发、取消、RequestModifier | |
SDWebImageDownloaderOperation | 单次下载任务,实现 SDWebImageDownloaderOperation 协议 | |
| 缓存 | SDImageCache | 内存 + 磁盘二级缓存,提供 query/store/remove,支持自定义 key、过期策略 |
SDMemoryCache / SDDiskCache | 内存层、磁盘层具体实现(5.x 可拆分) | |
| 解码 | SDImageCoder (协议) | 定义 Data ↔ Image 编解码,如 decodedImageWithData:options: |
SDImageCodersManager | 管理多个 Coder,按数据格式选择可用 Coder | |
SDWebImageImageIOCoder 等 | 内置 Coder 实现(JPEG/PNG/HEIC/…) | |
| 变换 | SDImageTransformer (协议) | 定义 Image → Image 变换,如 transformedImageWithImage:forKey: |
SDImagePipelineTransformer | 将多个 Transformer 串联为一条管线 | |
SDImageResizingTransformer 等 | 内置 Transformer 实现 | |
| 视图 | UIImageView+WebCache | 为 UIImageView 提供 sd_setImage(with:...)、sd_cancelCurrentImageLoad |
SDAnimatedImageView | 动图展示,配合 SDAnimatedImage | |
UIButton+WebCache 等 | 其他控件的扩展 |
2. 模块划分与依赖关系
下图从「模块」维度表示各层之间的依赖方向:视图扩展依赖 Manager,Manager 依赖 Loader/Cache,解码与变换在 Manager 内被调用,Loader 只产出 Data,Cache 只做存取。
flowchart TB
subgraph 视图层
V1[UIImageView+WebCache]
V2[UIButton+WebCache]
V3[SDAnimatedImageView]
end
subgraph 协调层
M[SDWebImageManager]
end
subgraph 加载层
L[SDWebImageDownloader]
end
subgraph 缓存层
C[SDImageCache]
end
subgraph 编解码层
CM[SDImageCodersManager]
CO[SDImageCoder 实现]
end
subgraph 变换层
T[SDImageTransformer 实现]
end
V1 --> M
V2 --> M
V3 --> M
M --> L
M --> C
M --> CM
M --> T
CM --> CO
3. 加载与缓存类结构
Loader 负责从网络(或自定义来源)获取数据;Cache 负责内存与磁盘的读写。Manager 持有两者引用,在单次请求中先问 Cache,未命中再调 Loader。
classDiagram
class SDWebImageManager {
-imageLoader: SDImageLoader
-imageCache: SDImageCache
+loadImage(with:options:context:progress:completed:)
-callLoadImage(with:options:context:progress:completed:)
}
class SDImageLoader {
<<protocol>>
+requestImageWithURL:options:context:progress:completed()
+canRequestImageForURL()
}
class SDWebImageDownloader {
-session: URLSession
-downloadQueue: NSOperationQueue
+downloadImageWithURL:options:progress:completed()
}
class SDImageCache {
-memoryCache: SDMemoryCache
-diskCache: SDDiskCache
+queryImageForKey:options:context:callback()
+storeImage:imageData:forKey:completion()
+removeImageForKey:withCompletion()
}
SDWebImageManager --> SDImageLoader : 使用
SDWebImageManager --> SDImageCache : 使用
SDWebImageDownloader ..|> SDImageLoader : 实现
- SDWebImageManager:对外提供
loadImage(with:...),内部先查imageCache,再根据需要调用imageLoader,最后根据 context 决定是否解码、变换并写回缓存。 - SDWebImageDownloader:实现
SDImageLoader协议,通过 URLSession 下载,支持并发数、超时、RequestModifier;单次下载封装为SDWebImageDownloaderOperation。 - SDImageCache:内存缓存通常用 NSCache 或自研 LRU,磁盘缓存为文件系统;query/store 的 key 由 Manager 根据 URL + context(含 transformer 等)生成。
4. 解码与变换类结构
Coder 将 Data 转为 Image(或反向);Transformer 将 Image 转为另一 Image。Manager 在「Loader 返回 Data 后」先选 Coder 解码,再按 context 中的 Transformer 做变换,得到最终 Image 再写入 Cache。
classDiagram
class SDWebImageManager {
-loadImage(with:...)
}
class SDImageCoder {
<<protocol>>
+decodedImageWithData:options()
+encodedDataWithImage:format:options()
+canDecodeFromData()
+canEncodeToFormat()
}
class SDImageCodersManager {
-coders: [SDImageCoder]
+addCoder()
+removeCoder()
+canDecodeFromData()
+decodedImageWithData:options()
}
class SDImageTransformer {
<<protocol>>
+transformerKey
+transformedImageWithImage:forKey()
}
class SDImagePipelineTransformer {
-transformers: [SDImageTransformer]
+transformerKey
+transformedImageWithImage:forKey()
}
SDWebImageManager ..> SDImageCodersManager : 解码时使用
SDWebImageManager ..> SDImageTransformer : 变换时使用
SDImageCodersManager --> SDImageCoder : 委托具体 Coder
SDImagePipelineTransformer ..|> SDImageTransformer : 实现
- SDImageCodersManager:持有一组
SDImageCoder,按canDecodeFromData:选出第一个能处理当前 Data 的 Coder 执行解码;编码同理。 - SDImagePipelineTransformer:持有一组
SDImageTransformer,按顺序对 Image 依次变换;其transformerKey通常由各子 Transformer 的 key 拼接而成,参与缓存 key 生成。
5. View 扩展与调用链
视图扩展(如 UIImageView+WebCache)是业务最常接触的入口:内部将「当前 URL、placeholder、options、context」交给 SDWebImageManager,并把返回的加载任务与 view 关联,以便在复用时取消。
sequenceDiagram
participant V as UIImageView
participant Ext as UIImageView+WebCache
participant M as SDWebImageManager
participant C as SDImageCache
participant L as SDWebImageDownloader
V->>Ext: sd_setImage(with: url, ...)
Ext->>Ext: sd_cancelCurrentImageLoad()
Ext->>M: loadImage(with: url, context: [...])
M->>C: queryImage(forKey:)
alt 缓存命中
C-->>M: image
M-->>Ext: completed(image, .memory/.disk)
else 未命中
M->>L: requestImageWithURL:...
L-->>M: data
M->>M: 解码 + 变换
M->>C: storeImage(forKey:)
M-->>Ext: completed(image, .none)
end
Ext->>V: imageView.image = image
- sd_setImage(with: placeholder: options: context: completed:):先对当前 view 取消未完成任务,再调
SDWebImageManager.shared.loadImage(with: url, options: options, context: context, progress: progress, completed: completed);在 completed 中把得到的 image 赋给imageView.image(并可选执行 transition 动画)。 - sd_cancelCurrentImageLoad():取消与该 view 绑定的 load 任务,避免 cell 复用时旧请求覆盖新图片。
将上述「核心类总览」「模块依赖」「Loader/Cache 类图」「Coder/Transformer 类图」「View 调用链」串联起来,即可形成对 SDWebImage 类结构图 的完整分析:入口在视图扩展,核心协调在 Manager,加载与缓存、解码与变换均为可插拔的协议实现,便于扩展与测试。
五、与系统及业界实践的衔接
1. Apple 图像与图形最佳实践
Apple 在 WWDC 2018「Image and Graphics Best Practices」[1] 中强调:
- 在后台线程进行解码与下采样,避免在主线程做重 CPU 工作导致的卡顿。
- 解码时即做下采样,使解码后的图像缓冲与显示尺寸匹配,降低内存与 CPU。
- 预取(Prefetch):在列表等场景提前准备即将显示的图像,避免在滚动时才开始解码。
SDWebImage 的解码与变换均在后台队列执行,且支持按尺寸/字节限制的缩略图解码,与上述建议一致。其 Prefetch 能力(如 UITableView 的 prefetch 结合 sd_setImageWithURL:)可在业务层配合使用,实现「提前解码、避免滚动时卡顿」。
Force Decode 策略(5.17+):SDWebImage 引入 SDImageForceDecodePolicy,用于控制是否在加载管线中强制解码(将延迟解码的图片提前转为位图)。在部分场景下可避免在渲染阶段才触发 CA 的帧缓冲拷贝,从而降低主线程峰值与内存抖动;具体策略可根据「是否使用自定义渲染」「是否配合 Transformer」等选择,详见官方文档与 CHANGELOG。
2. 移动端图像管线研究简述
在移动端部署图像管线(含解码、缩放、轻量级「变换」)方面,业界与学界有大量工作:
- FlexiViT [7] 等通过可变的 patch 尺寸在训练与推理时平衡精度与速度;
- NanoFLUX [8]、SnapGen [9] 等关注在移动设备上的高效图像生成与压缩。
这些工作与「在端侧做高效解码与分辨率控制」的目标一致:在有限算力与内存下,通过解码阶段控制(如缩略图、渐进解码)和管线化处理(解码 → 变换 → 缓存)提升体验。SDWebImage 的 Decoder + Transformer 双管线正是这一思路在「图片加载库」中的具体实现。
六、使用案例与原理分析
0. 框架结构速览
0.1 实现原理
- 架构图(UML 类图)
- 流程图(方法调用顺序图)
0.2 目录结构
- Downloader\
- SDWebImageDownloader\
- SDWebImageDownloaderOperation
- Cache\
- SDImageCache
- Utils\
- SDWebImageManager\
- SDWebImageDecoder\
- SDWebImagePrefetcher
- Categories\
- UIView+WebCacheOperation\
- UIImageView+WebCache\
- UIImageView+HighlightedWebCache\
- UIButton+WebCache\
- MKAnnotationView+WebCache\
- NSData+ImageContentType\
- UIImage+GIF\
- UIImage+MultiFormat\
- UIImage+WebP
- Other\
- SDWebImageOperation(协议)\
- SDWebImageCompat(宏定义、常量、通用函数)
0.3 相关类名与功能描述
- SDWebImageDownloader:是专门用来下载图片和优化图片加载的,跟缓存没有关系
- SDWebImageDownloaderOperation:继承于 NSOperation,用来处理下载任务的
- SDImageCache:用来处理内存缓存和磁盘缓存(可选)的,其中磁盘缓存是异步进行的,因此不会阻塞主线程
- SDWebImageManager:作为 UIImageView+WebCache 背后的默默付出者,主要功能是将图片下载(SDWebImageDownloader)和图片缓存(SDImageCache)两个独立的功能组合起来
- SDWebImageDecoder:图片解码器,用于图片下载完成后进行解码
- SDWebImagePrefetcher:预下载图片,方便后续使用,图片下载的优先级低,其内部由 SDWebImageManager 来处理图片下载和缓存
- UIView+WebCacheOperation:用来记录图片加载的 operation,方便需要时取消和移除图片加载的 operation
- UIImageView+WebCache:集成 SDWebImageManager 的图片下载和缓存功能到 UIImageView 的方法中,方便调用方的简单使用
- UIImageView+HighlightedWebCache:跟 UIImageView+WebCache 类似,也是包装了 SDWebImageManager,只不过是用于加载 highlighted 状态的图片
- UIButton+WebCache:跟 UIImageView+WebCache 类似,集成 SDWebImageManager 的图片下载和缓存功能到 UIButton 的方法中,方便调用方的简单使用
- MKAnnotationView+WebCache:跟 UIImageView+WebCache 类似
- NSData+ImageContentType:用于获取图片数据的格式(JPEG、PNG 等)
- UIImage+GIF:用于加载 GIF 动图
- UIImage+MultiFormat:根据不同格式的二进制数据转成 UIImage 对象
- UIImage+WebP:用于解码并加载 WebP 图片
0.4 工作流程
- 入口 setImageWithURL:placeholderImage:options: 会先把 placeholderImage 显示,然后 SDWebImageManager 根据 URL 开始处理图片。
- 进入 SDWebImageManager-downloadWithURL:delegate:options:userInfo: 交给 SDImageCache 从缓存查找图片是否已经下载 queryDiskCacheForKey:delegate:userInfo:。
- 先从内存图片缓存查找是否有图片,如果内存中已经有图片缓存,SDImageCacheDelegate 回调 imageCache:didFindImage:forKey:userInfo: 到 SDWebImageManager。
- SDWebImageManagerDelegate 回调 webImageManager:didFinishWithImage: 到 UIImageView+WebCache 等前端展示图片。
- 如果内存缓存中没有,生成 NSInvocationOperation 添加到队列开始从硬盘查找图片是否已经缓存。
- 根据 URLKey 在硬盘缓存目录下尝试读取图片文件。这一步是在 NSOperation 进行的操作,所以回主线程进行结果回调 notifyDelegate:。
- 如果从硬盘读取到了图片,将图片添加到内存缓存中(如果空闲内存过小,会先清空内存缓存)。SDImageCacheDelegate 回调 imageCache:didFindImage:forKey:userInfo: 进而回调展示图片。
- 如果从硬盘缓存目录读取不到图片,说明所有缓存都不存在该图片,需要下载图片,回调 imageCache:didNotFindImageForKey:userInfo:。
- 共享或重新生成一个下载器 SDWebImageDownloader 开始下载图片。
- 图片下载由 NSURLConnection(3.8.0 之后使用了 NSURLSession),实现相关 delegate 来判断图片下载中、下载完成和下载失败。
- connection:didReceiveData: 中利用 ImageIO 做了按图片下载进度加载效果。connectionDidFinishLoading: 数据下载完成后交给 SDWebImageDecoder 做图片解码处理。
- 图片解码处理在一个 NSOperationQueue 完成,不会拖慢主线程 UI。如果有需要对下载的图片进行二次处理,最好也在这里完成,效率会好很多。
- 在主线程 notifyDelegateOnMainThreadWithInfo: 宣告解码完成,imageDecoder:didFinishDecodingImage:userInfo: 回调给 SDWebImageDownloader。
- imageDownloader:didFinishWithImage: 回调给 SDWebImageManager 告知图片下载完成。
- 通知所有的 downloadDelegates 下载完成,回调给需要的地方展示图片。
- 将图片保存到 SDImageCache 中,内存缓存和硬盘缓存同时保存。写文件到硬盘也在以单独 NSInvocationOperation 完成,避免拖慢主线程。
- SDImageCache 在初始化的时候会注册一些消息通知,在内存警告或退到后台的时候清理内存图片缓存,应用结束的时候清理过期图片。
- SDWebImagePrefetcher 可以预先下载图片,方便后续使用。
1. 典型使用案例
1.1 列表 Cell 中加载缩略图(防错位 + 下采样)
在 UITableView / UICollectionView 的 cell 中,若不限制图片尺寸,大图会带来内存峰值与卡顿;且 cell 复用时需避免「先显示旧图再被新图覆盖」的错位。SDWebImage 通过 URL 绑定 与 取消机制 解决错位,通过 Transformer 限制尺寸 控制内存。
// Cell 内
func configure(with url: URL) {
imageView.sd_cancelCurrentImageLoad()
let transformer = SDImageResizingTransformer(
size: CGSize(width: 120, height: 120),
scaleMode: .aspectFill
)
imageView.sd_setImage(
with: url,
placeholderImage: UIImage(named: "placeholder"),
context: [.imageTransformer: transformer]
)
}
要点:sd_cancelCurrentImageLoad() 会取消该 view 上未完成的请求,新 URL 加载完成后才设置,避免复用时显示错误图片。
1.2 预取(Prefetch)提前解码
利用系统预取 API 在 cell 尚未显示时就开始加载,滚动时直接从缓存读取,减少卡顿。
// 实现 UICollectionViewDataSourcePrefetching
func collectionView(_ collectionView: UICollectionView, prefetchItemsAt indexPaths: [IndexPath]) {
let urls = indexPaths.compactMap { model(at: $0).imageURL }
urls.forEach { url in
SDWebImagePrefetcher.shared.prefetchURLs([url])
}
}
// 可选:取消不再需要的预取
func collectionView(_ collectionView: UICollectionView, cancelPrefetchingForItemsAt indexPaths: [IndexPath]) {
let urls = indexPaths.compactMap { model(at: $0).imageURL }
SDWebImagePrefetcher.shared.cancelPrefetching(for: urls)
}
1.3 多设备/多 Channel 下的加载
在多设备场景(如同一 URL 在不同 Channel 下需要不同尺寸)中,通过 context 传入不同的 Transformer 或 Cache,使同一 URL 对应多条缓存条目。
// 列表用小图
imageView.sd_setImage(with: url, context: [
.imageTransformer: SDImageResizingTransformer(size: CGSize(width: 80, height: 80), scaleMode: .aspectFill)
])
// 详情用原图或大图
detailImageView.sd_setImage(with: url) // 不传 transformer,用原图
1.4 占位图 + 加载完成过渡动画
通过 sd_imageTransition 在图片从网络加载完成后做淡入等过渡,提升观感。
imageView.sd_imageTransition = .fade(0.25)
imageView.sd_setImage(with: url, placeholderImage: placeholder)
1.5 仅下载不展示(后台缓存)
希望提前把图片下载并写入缓存,供后续使用,而不绑定到某个 view。
SDWebImageManager.shared.loadImage(
with: url,
options: [],
progress: nil
) { image, data, error, cacheType, finished, url in
if let image = image, finished {
// 已缓存,可做后续逻辑
}
}
1.6 完成回调与错误处理
通过 completed 区分来源(内存/磁盘/网络)并处理失败与取消。
imageView.sd_setImage(with: url, placeholderImage: placeholder) { image, error, cacheType, url in
if let error = error {
// 可根据 error 类型提示用户或降级
return
}
switch cacheType {
case .none: break // 本次从网络加载
case .memory: break // 从内存缓存
case .disk: break // 从磁盘缓存
@unknown default: break
}
}
2. 更多使用案例与代码
2.1 UITableViewCell 完整示例(含复用与尺寸)
class PhotoCell: UITableViewCell {
static let reuseId = "PhotoCell"
@IBOutlet weak var photoImageView: UIImageView!
@IBOutlet weak var progressView: UIProgressView!
override func prepareForReuse() {
super.prepareForReuse()
photoImageView.sd_cancelCurrentImageLoad()
photoImageView.image = nil
progressView.progress = 0
}
func configure(with url: URL) {
let size = photoImageView.bounds.size
let transformer = SDImageResizingTransformer(
size: size.isEmpty ? CGSize(width: 120, height: 120) : size,
scaleMode: .aspectFill
)
photoImageView.sd_setImage(
with: url,
placeholderImage: UIImage(named: "placeholder"),
context: [.imageTransformer: transformer],
progress: { [weak self] received, total, _ in
guard let self = self, total > 0 else { return }
DispatchQueue.main.async {
self.progressView.progress = Float(received) / Float(total)
}
},
completed: { [weak self] image, error, _, _ in
DispatchQueue.main.async {
self?.progressView.isHidden = (image != nil)
}
}
)
}
}
2.2 UIButton 设置网络图片
// 设置不同 state 的图片
button.sd_setImage(with: url, for: .normal, placeholderImage: UIImage(named: "btn_placeholder"))
button.sd_setImage(with: highlightedURL, for: .highlighted)
button.sd_setBackgroundImage(with: backgroundURL, for: .normal)
// 带圆角与完成回调
let transformer = SDImageRoundCornerTransformer(radius: 8, corners: .allCorners, borderWidth: 0, borderColor: nil)
button.sd_setImage(with: url, for: .normal, placeholderImage: nil, context: [.imageTransformer: transformer]) { _, error, _, _ in
if error != nil { print("加载失败") }
}
2.3 自定义缓存键(同一 URL 多用途)
当同一 URL 在不同业务下需要不同缓存(例如列表用缩略图、详情用原图)时,可用 cacheKeyFilter 或自定义 key。
// 方式一:通过 context 的 cacheKeyFilter 生成不同 key
let listKeyFilter: SDWebImageCacheKeyFilter = { url in
return "list_\(url?.absoluteString ?? "")" as NSString
}
imageView.sd_setImage(with: url, context: [.cacheKeyFilter: listKeyFilter])
let detailKeyFilter: SDWebImageCacheKeyFilter = { url in
return "detail_\(url?.absoluteString ?? "")" as NSString
}
detailImageView.sd_setImage(with: url, context: [.cacheKeyFilter: detailKeyFilter])
// 方式二:在业务层用不同 URL 或 query 区分(如服务端支持 ?size=thumb)
let listURL = url.appendingPathComponent("?size=thumb")
let detailURL = url
imageView.sd_setImage(with: listURL, context: [.cacheKeyFilter: listKeyFilter])
detailImageView.sd_setImage(with: detailURL, context: [.cacheKeyFilter: detailKeyFilter])
2.4 请求修饰(Header、Token、超时)
需要带鉴权或自定义 Header 时,使用 requestModifier。
let modifier = SDWebImageDownloaderRequestModifier { request in
var r = request
r.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
r.setValue("image/webp,image/*,*/*;q=0.8", forHTTPHeaderField: "Accept")
r.timeoutInterval = 30
return r
}
imageView.sd_setImage(
with: url,
context: [.requestModifier: modifier]
)
2.5 下载进度条 + 占位 + 过渡动画
imageView.sd_imageTransition = .fade(0.3)
imageView.sd_setImage(
with: url,
placeholderImage: UIImage(named: "placeholder"),
options: [.retryFailed],
progress: { [weak progressView] received, total, _ in
guard let pv = progressView, total > 0 else { return }
DispatchQueue.main.async {
pv.progress = Float(received) / Float(total)
pv.isHidden = false
}
},
completed: { [weak progressView] image, _, cacheType, _ in
DispatchQueue.main.async {
progressView?.isHidden = true
if image == nil { /* 显示失败占位 */ }
}
}
)
2.6 动图 GIF(SDAnimatedImageView)
// 使用 SDAnimatedImageView 播放 GIF/WebP/APNG
let animatedImageView = SDAnimatedImageView()
animatedImageView.sd_setImage(with: gifURL, placeholderImage: nil)
// 仅加载动图第一帧作为封面(节省内存)
animatedImageView.sd_setImage(with: gifURL, placeholderImage: nil, options: [.decodeFirstFrameOnly])
// 渐进式加载动图(边下边播)
animatedImageView.sd_setImage(with: gifURL, placeholderImage: nil, options: [.progressiveLoad])
2.7 缓存查询与手动存储
let cache = SDImageCache.shared
let key = url.absoluteString
// 查询是否已缓存
cache.containsImage(forKey: key) { cacheType in
switch cacheType {
case .none: print("未缓存")
case .memory: print("在内存")
case .disk: print("在磁盘")
@unknown default: break
}
}
// 从缓存读取(不触发下载)
cache.queryImage(forKey: key, options: nil, context: nil) { image, data, cacheType in
if let image = image {
imageView.image = image
}
}
// 手动写入缓存(例如本地生成或从相册来的图)
cache.store(image, forKey: key, completion: nil)
2.8 自定义 Transformer 示例
实现 SDImageTransformer 协议,对已解码图像做自定义绘制或滤镜(以下方法名以 SDWebImage 5.x 协议为准,实际请参照当前版本头文件)。
// 实现协议:为图片加灰色边框
class GrayBorderTransformer: NSObject, SDImageTransformer {
var transformerKey: String { "GrayBorder(\(borderWidth))" }
let borderWidth: CGFloat
init(borderWidth: CGFloat = 2) { self.borderWidth = borderWidth }
func transformedImage(with image: UIImage, forKey key: String) -> UIImage? {
let size = image.size
UIGraphicsBeginImageContextWithOptions(size, false, image.scale)
defer { UIGraphicsEndImageContext() }
image.draw(at: .zero)
UIColor.gray.setStroke()
let path = UIBezierPath(rect: CGRect(origin: .zero, size: size).insetBy(dx: borderWidth/2, dy: borderWidth/2))
path.lineWidth = borderWidth
path.stroke()
return UIGraphicsGetImageFromCurrentImageContext()
}
}
// 使用
let transformer = GrayBorderTransformer(borderWidth: 3)
imageView.sd_setImage(with: url, context: [.imageTransformer: transformer])
2.9 强制刷新与仅从缓存读取
// 忽略缓存,强制重新下载(适用于需要刷新内容的场景)
imageView.sd_setImage(with: url, options: [.forceRefresh])
// 仅从缓存读取,没有则显示占位或报错(离线/省流量场景)
imageView.sd_setImage(with: url, options: [.onlyFromCache]) { image, error, _, _ in
if image == nil { print("缓存中无此图") }
}
2.10 Objective-C 常用写法
// 基础加载
[imageView sd_setImageWithURL:url placeholderImage:[UIImage imageNamed:@"placeholder"]];
// 带 context 的 Transformer
id<SDImageTransformer> transformer = [SDImagePipelineTransformer transformerWithTransformers:@[
[SDImageResizingTransformer transformerWithSize:CGSizeMake(100, 100) scaleMode:SDImageScaleModeFill],
[SDImageRoundCornerTransformer transformerWithRadius:10 corners:SDRectCornerAllCorners borderWidth:0 borderColor:nil]
]];
[imageView sd_setImageWithURL:url placeholderImage:nil context:@{SDWebImageContextImageTransformer: transformer}];
// 取消当前加载
[imageView sd_cancelCurrentImageLoad];
// 完成回调
[imageView sd_setImageWithURL:url completed:^(UIImage * _Nullable image, NSError * _Nullable error, SDImageCacheType cacheType, NSURL * _Nullable imageURL) {
if (error) { NSLog(@"加载失败: %@", error); }
}];
3. 核心流程原理分析
3.1 Manager 协调的完整链路
SDWebImageManager 是整条「加载 → 解码 → 变换 → 缓存」的协调者,其内部逻辑可概括为:
flowchart TD
A[loadImageWithURL] --> B{查缓存 key}
B --> C[先查内存]
C --> D{命中?}
D -->|是| E[回调 .memory]
D -->|否| F[再查磁盘]
F --> G{命中?}
G -->|是| H[解码/反序列化]
H --> I[写回内存]
I --> E
G -->|否| J[构造 Loader 任务]
J --> K[Loader 返回 Data]
K --> L[Coder 解码]
L --> M{有 Transformer?}
M -->|是| N[Transformer 变换]
M -->|否| O[得到 Image]
N --> O
O --> P[写内存+磁盘缓存]
P --> Q[回调 .none 或 .disk]
要点:
- 缓存 key 由 URL(或自定义 key)与 context(如 transformer、cacheKeyFilter)共同决定,同一 URL 不同 transformer 会得到不同 key。
- 解码与变换均在 Manager 持有的串行/并发队列 中执行,回调通过
dispatch_async(main_queue)回到主线程,便于更新 UI。
3.2 回调与线程模型
- progress:在下载进度回调所在线程(多为 URLSession 回调线程),若需更新 UI 需自行切主线程。
- completed:SDWebImage 内部会派发到主线程再调用,因此 completed 里可直接操作 UI。
- 取消:当再次对同一 view 调用
sd_setImage(with: newURL)时,会取消该 view 上此前由 SDWebImage 发起的任务,completed 仍可能被调用一次(cancel 语义),可通过SDWebImageOption或检查finished区分。
3.3 缓存键与 Transformer 的关系
变换后的图片会以「新 key」写入缓存:通常为 key + transformerIdentifier 或等价组合。因此:
- 原图:key = url.absoluteString(或自定义)。
- 变换图:key = f(url, transformer),例如
url.absoluteString + "_" + transformer.identifier。
这样同一 URL 可同时存在「原图」与「缩放版」「圆角版」等多份缓存,互不覆盖;原图也可通过 SDWebImageContextOriginalImageCache 写入单独缓存实例,供大图页等使用。
七、设计模式与编程思想
1. 设计模式应用
SDWebImage 在架构上大量运用经典设计模式,使扩展与维护成本可控。
| 模式 | 在 SDWebImage 中的体现 | 作用 |
|---|---|---|
| 外观 / 门面(Facade) | SDWebImageManager 对外提供 loadImage(with:options:progress:completed:),内部协调 Loader、Cache、Coder、Transformer,调用方无需关心多级缓存与管线顺序 | 简化使用、隐藏复杂度 |
| 策略(Strategy) | SDImageTransformer、SDImageCoder 均为协议,多种实现可替换(Resizing、RoundCorner、WebP Coder 等),通过 context 或注册表注入 | 算法/行为可插拔,易扩展新格式与新变换 |
| 责任链 / 管道(Chain of Responsibility / Pipeline) | SDImagePipelineTransformer 将多个 Transformer 串联;解码管线中 Coder 的选取也可视为「按责任链匹配 canDecodeFromData」 | 多步处理顺序清晰,便于组合与复用 |
| 单例 + 共享依赖(Singleton) | SDWebImageManager.shared、SDImageCache.shared、SDWebImageDownloader.shared 提供默认实例,同时支持传入自定义 Cache/Loader 以打破单例 | 全局统一入口,又保留可测试性与多实例能力 |
| 观察者 / 回调(Observer / Callback) | 通过 progress、completed 闭包通知进度与结果;部分能力通过 delegate 扩展 | 异步结果与 UI 解耦 |
| 工厂思想(Factory) | SDImageCodersManager 根据 Data 格式选择 Coder;Loader 根据 URL 或 scheme 选择具体 Loader 实现 | 创建逻辑集中,便于支持新协议与新格式 |
类图关系(概念层):
classDiagram
class SDWebImageManager {
-cache: SDImageCache
-loader: SDImageLoader
+loadImage(with:options:progress:completed:)
}
class SDImageCache {
+store(_:forKey:)
+queryImage(forKey:options:callback:)
}
class SDImageLoader {
+loadImage(with:options:progress:completed:)
}
class SDImageCoder {
<<protocol>>
+decodedImageWithData:options:
+canDecodeFromData:
}
class SDImageTransformer {
<<protocol>>
+transform(image:key:)
}
SDWebImageManager --> SDImageCache : 使用
SDWebImageManager --> SDImageLoader : 使用
SDWebImageManager ..> SDImageCoder : 解码时选用
SDWebImageManager ..> SDImageTransformer : 变换时选用
2. 编程思想精华
SDWebImage 的编程思想可提炼为以下几点,对理解与模仿其设计很有帮助。
2.1 协议导向与「可替换实现」
- Coder、Transformer、Loader、Cache 均以协议或抽象接口呈现,具体实现可替换、可组合。
- 新增一种图片格式或一种变换,只需实现对应协议并注册,无需改动 Manager 核心流程。这体现了开闭原则:对扩展开放,对修改关闭。
2.2 管线化与单一职责
- 把「从 URL 到屏幕」拆成:加载 → 解码 → 变换 → 缓存 → 展示,每一步只做一件事。
- 解码只关心 Data → Image,变换只关心 Image → Image,缓存只关心存储与查找。单一职责使每块可独立测试、优化和扩展;管线化则使数据流清晰,便于加日志、监控和插桩。
2.3 缓存键与「同一资源多形态」
- 通过 key = f(URL, context) 的设计,同一 URL 可以对应「原图」「缩略图」「圆角图」等多条缓存,避免重复下载,又满足不同场景对尺寸/形态的需求。这体现了用键设计表达业务差异的思想。
2.4 后台处理与主线程回调
- 解码、变换、磁盘 I/O 均在后台队列执行,completed 回调派发到主线程,兼顾性能与 UI 安全。这是移动端异步加载库的通用范式:重活放后台,结果回主线程。
2.5 取消与生命周期绑定
- View 扩展(如
UIImageView+WebCache)会把「当前正在进行的任务」与 view 绑定,当对同一 view 发起新请求时自动取消旧请求,避免错位和浪费。这体现了生命周期与请求绑定的思想,在列表场景中尤为重要。
2.6 配置通过 Context 透传
- 不通过全局单例属性堆砌配置,而是通过 SDWebImageContext 在单次请求中传入 Cache、Transformer、Loader、CacheKeyFilter 等,使「同一 App 内不同页面/模块」可使用不同策略,且易于单元测试时注入 mock。
SDWebImage 编程思想精华一览:
| 思想 | 在框架中的体现 |
|---|---|
| 协议导向、可替换 | Coder / Transformer / Loader 协议化,新格式、新变换仅需实现协议并注册 |
| 管线化、单一职责 | 加载 → 解码 → 变换 → 缓存 → 展示,每步职责单一,便于扩展与测试 |
| 键设计表达多形态 | 同一 URL 通过 key = f(URL, context) 支持原图、缩略图、圆角图等多条缓存 |
| 后台处理、主线程回调 | 重 CPU/IO 在后台队列,completed 回主线程,兼顾性能与 UI 安全 |
| 生命周期绑定取消 | View 与当前任务绑定,新请求自动取消旧请求,避免列表错位 |
| Context 透传配置 | 单次请求级配置,避免全局状态,利于多策略并存与测试注入 |
八、使用示例与最佳实践
1. 使用内置变换器(缩放 + 圆角)
let transformer = SDImagePipelineTransformer(transformers: [
SDImageResizingTransformer(size: CGSize(width: 300, height: 300), scaleMode: .fill),
SDImageRoundCornerTransformer(radius: 20, corners: .allCorners, borderWidth: 0, borderColor: nil)
])
imageView.sd_setImage(with: url, placeholderImage: nil, context: [.imageTransformer: transformer])
2. 仅下载并变换、不写缓存
SDWebImageManager.shared.loadImage(
with: url,
options: [.fromLoaderOnly],
context: [.storeCacheType: SDImageCacheType.none.rawValue, .imageTransformer: transformer],
progress: nil
) { image, _, _, _, _, _ in
// 使用变换后的 image
}
3. 渐进式加载(渐进解码)
imageView.sd_setImage(with: url, placeholderImage: nil, options: [.progressiveLoad])
4. 自定义 Coder 注册(以 WebP 为例)
SDImageCodersManager.shared.addCoder(SDImageWebPCoder.shared)
5. 最佳实践小结
- 列表/网格:cell 内先
sd_cancelCurrentImageLoad(),再sd_setImage,并配合 Transformer 限制尺寸或使用下采样选项,减少内存与错位。 - 预取:用
SDWebImagePrefetcher或系统 prefetch API 提前加载即将出现的图片,滚动时优先命中缓存。 - 大图/详情:列表用缩小版 Transformer,详情页用原图或单独 OriginalImageCache,避免重复下载。
- 动图:使用
SDAnimatedImageView+SDAnimatedImage,并视情况注册 GIF/WebP/APNG 等 Coder。 - 扩展与测试:自定义 Coder/Transformer 通过协议实现并注册;通过 context 注入自定义 Cache/Loader 便于单测与多策略并存。
九、常见面试题
1. 图片文件缓存的时间有多长?
一周。_maxCacheAge = kDefaultCacheMaxCacheAge
2. SDWebImage 的内存缓存是用什么实现的?
NSCache
3. SDWebImage 的最大并发数是多少?
maxConcurrentDownloads = 6(程序固定,可通过属性调整)
4. SDWebImage 支持动图吗?GIF
支持。示例:
#import <ImageIO/ImageIO.h>
[UIImage animatedImageWithImages:images duration:duration];
5. SDWebImage 是如何区分不同格式的图像的?
- 根据图像数据第一个字节来判断
- PNG:压缩比没有 JPG 高,但无损压缩,解压缩性能高,苹果推荐的图像格式
- JPG:压缩比最高的一种图片格式,有损压缩,最多使用的场景如照相机,解压缩性能不好
- GIF:序列帧动图,只支持 256 种颜色,曾流行于 1998~1999,有专利
6. SDWebImage 缓存图片的名称是怎么确定的?
- 使用
md5对完整 URL 做散列,得到 32 位字符串作为文件名;若单纯用文件名保存,重名几率高
7. SDWebImage 的内存警告是如何处理的?
- 利用通知中心观察:
UIApplicationDidReceiveMemoryWarningNotification:接收到内存警告后执行clearMemory,清理内存缓存UIApplicationWillTerminateNotification:接收到应用将要终止后执行cleanDisk,清理磁盘缓存UIApplicationDidEnterBackgroundNotification:接收到应用进入后台后执行backgroundCleanDisk,后台清理磁盘
- 通过以上通知监听,保证缓存文件大小在控制范围内;
clearDisk可清空磁盘缓存,删除缓存目录中全部文件
参考文献
[1] Apple. Image and Graphics Best Practices. WWDC 2018, Session 219.
[2] Stack Overflow / Apple. Creating a thumbnail from UIImage using CGImageSourceCreateThumbnailAtIndex.
[3] Apple. Image decompression strategies for performance. developer.apple.com/forums/thread/653738.
[4] Ctrl.blog. Progressive JPEG loading; Google 研究:渐进解码约 3 倍于 baseline 的 CPU 开销.
[5] Wikipedia. Digital image processing.
[6] SDWebImage. Advanced Usage - Image Transformer, Custom Coder. GitHub Wiki.
[7] Beyer et al. FlexiViT: One Model for All Patch Sizes. CVPR 2023.
[8] NanoFLUX. Distillation-Driven Compression of Large Text-to-Image Generation Models for Mobile Devices. arXiv.
[9] SnapGen. Taming High-Resolution Text-to-Image Models for Mobile Devices. arXiv 2024.