02-研究优秀开源框架@图层处理@iOS | Kingfisher 框架:从使用到原理解析
📋 目录
- 一、Kingfisher 概述与历史演进
- 二、图像处理管线(ImageProcessor Pipeline)
- 三、解码、缓存与处理器的协同
- 四、类结构图分析
- 五、与系统及业界实践的衔接
- 六、设计模式与编程思想
- 七、使用示例与最佳实践
- 延伸阅读(掘金系列)
- 参考文献
一、Kingfisher 概述与历史演进
1. 框架简介
Kingfisher 是一款面向 Apple 平台(iOS / macOS / tvOS / watchOS)的纯 Swift 异步图片下载与缓存库,由 onevcat(王巍)维护。其「图层处理」相关能力以 ImageProcessor 为核心:在「从数据到图像」以及「从图像到图像」的管线中,完成解码、缩放、圆角、模糊、着色等处理,并与 ImageCache(内存 + 磁盘)、ImageDownloader 协同,形成「请求 → 缓存查询 → 下载 → 处理 → 缓存 → 展示」的完整流程 [1][2]。
与 SDWebImage(Objective-C 为主)相比,Kingfisher 采用协议导向与 Options 模式,图层处理通过统一的 ImageProcessor 协议和 ImageProcessItem 双态输入抽象,便于扩展与组合。
2. 技术演进与版本脉络
Kingfisher 的图层处理能力随版本逐步增强,并与缓存、下载模块解耦清晰。
| 阶段 | 版本/时期 | 图层处理与相关能力 |
|---|---|---|
| 早期 | 3.x | 基础下载与缓存,简单图片处理 |
| 缓存与处理器 | 3.10 | 带 ImageProcessor 的缓存策略:先查已处理图,若无再查原图,避免重复下载 [3] |
| 架构升级 | 5.0 | MemoryStorage / DiskStorage 分离,可缓存原始 Data,完善 KingfisherError,处理管线与缓存键绑定 [4] |
| 下采样修复 | 5.3 | 下采样 scale 与内存表现修复:从原图加载下采样结果时的 scale 与内存问题 [5] |
| 动图与序列化 | 7.8 | 磁盘缓存取回动图时正确使用请求中的 processor [6] |
| 渐进式 JPEG | 8.3 | SwiftUI KFImage 支持 progressiveJPEG 修饰符 [7] |
5.0 是重要分水岭:处理管线与缓存键(含 processorIdentifier)深度结合,使「同一 URL + 不同 Processor」对应不同缓存条目,原图与处理后图可并存。
3. 图层处理在整体架构中的位置
下图概括从「资源(URL / ImageDataProvider)」到「显示到视图」的流程,并标出 ImageProcessor 所在阶段。
flowchart LR
subgraph 输入
A[URL / ImageDataProvider]
end
subgraph 获取数据
B[ImageDownloader / Provider.data]
end
subgraph 处理层
C[Data]
D[ImageProcessor 管线]
E[KFCrossPlatformImage]
end
subgraph 缓存与输出
F[ImageCache]
G[ImageView / KFImage]
end
A --> B --> C --> D --> E --> F --> G
要点:
- ImageProcessor 的输入可以是 Data(未解码)或 Image(已解码);输出为 Image。因此它同时覆盖「Data → Image」(如 DefaultImageProcessor、DownsamplingImageProcessor)和「Image → Image」(如 RoundCorner、Blur、Resizing)两类操作。
- 处理在 KingfisherManager 协调下、通常在后台队列执行,避免阻塞主线程,符合 Apple 图像最佳实践 [8]。
二、图像处理管线(ImageProcessor Pipeline)
1. ImageProcessItem 与双态输入
Kingfisher 用 ImageProcessItem 表示处理器的输入,有两种情况 [9]:
public enum ImageProcessItem: Sendable {
/// 已解码的图像,处理器在其上做几何/像素变换
case image(KFCrossPlatformImage)
/// 原始数据,处理器需负责解码(或解码+变换)
case data(Data)
}
设计意图:
- 统一接口:同一套管线既可处理「仅解码」(Data → Image),也可处理「仅变换」(Image → Image),或「解码 + 变换」(Data 经多个 Processor 最终得到 Image)。
-
避免重复解码:当管线中第一个 Processor 已将 Data 转为 Image 后,后续 Processor 收到
.image(...),只需做几何/滤镜等操作,无需再次解码。
数据流概念:
flowchart LR
subgraph 管线输入
I[Data]
end
subgraph P1[Processor 1]
I --> D1[解码/下采样]
D1 --> O1[Image]
end
subgraph P2[Processor 2]
O1 --> D2[圆角/缩放等]
D2 --> O2[Image]
end
O2 --> Out[输出]
2. ImageProcessor 协议与标识符
ImageProcessor 协议是 Kingfisher 图层处理的核心抽象 [9][10]:
协议 ImageProcessor:
属性 identifier: String // 唯一标识,参与缓存键
方法 process(item: ImageProcessItem, options: KingfisherParsedOptionsInfo) -> KFCrossPlatformImage?
-
identifier:相同功能/参数的 Processor 应返回相同字符串,用于缓存键。官方建议使用反向域名(如
com.onevcat.Kingfisher.RoundCornerImageProcessor(20)),且不要与DefaultImageProcessor的""冲突。 -
process:返回
nil表示处理失败,管线会报错并中止;若输入已是.image且当前步骤可透传,可返回原图以继续后续 Processor。
伪代码:管线执行:
函数 runPipeline(item: ImageProcessItem, processors: [ImageProcessor], options) -> Image?:
current = item
对每个 p in processors:
若 current 为 .data 且 p 只支持 .image:
current = .image(DefaultImageProcessor.default.process(current, options))
若 current 为 nil: 返回 nil
next = p.process(current, options)
若 next 为 nil: 返回 nil
current = .image(next)
返回 current
许多内置 Processor(如 RoundCorner、Blur)在收到 .data 时,会先通过 DefaultImageProcessor.default |> self 将 Data 解码为 Image,再对 Image 做自身变换,从而复用同一套协议。
3. 下采样(Downsampling)与 Resizing 的区分
Kingfisher 明确区分两种「变小」的方式,对应不同的内存与 CPU 成本 [10][11]。
3.1 DownsamplingImageProcessor
- 输入:仅 Data(压缩数据)。在解码阶段直接生成小尺寸位图,而不是先解码全图再缩放。
-
实现:基于 ImageIO 的
CGImageSourceCreateThumbnailAtIndex,通过kCGImageSourceThumbnailMaxPixelSize等选项限制最大边长,在解码器内部只生成缩略图级像素缓冲。 - 优势:内存占用与目标尺寸相关,避免「先全图解码」的峰值;大图列表、头像等场景推荐使用。
下采样算法步骤(与 Kingfisher / ImageIO 语义一致):
函数 Downsample(data: Data, size: CGSize) -> Image?:
1. maxDimensionInPixels = max(size.width, size.height) * scale
2. source = CGImageSourceCreateWithData(data, nil)
3. options = {
kCGImageSourceCreateThumbnailFromImageAlways: true,
kCGImageSourceCreateThumbnailWithTransform: true,
kCGImageSourceShouldCacheImmediately: true,
kCGImageSourceThumbnailMaxPixelSize: maxDimensionInPixels
}
4. cgImage = CGImageSourceCreateThumbnailAtIndex(source, 0, options)
5. 由 cgImage 构造 UIImage/NSImage 并返回
注意:size 不能为 (0, 0),否则会触发 "Processing image failed. Processor: DownsamplingImageProcessor" [11];在列表 cell 中应使用 cell 或目标视图的 bounds 计算合理 size。
3.2 ResizingImageProcessor
-
输入:一般为 Image(或通过 DefaultImageProcessor 先解码的 Data)。对已解码的位图做缩放,支持
ContentMode(如 aspectFit、aspectFill)。 - 实现:在像素缓冲上做几何变换(绘制到目标尺寸),会先占用全图解码的内存,再产生缩放后的新缓冲。
- 适用:已解码图、或必须对 Image 做精确尺寸/比例控制时使用;若从 Data 缩小,应优先 DownsamplingImageProcessor。
对比小结:
| 维度 | DownsamplingImageProcessor | ResizingImageProcessor |
|---|---|---|
| 输入 | Data | Image(或 Data 经 Default 解码) |
| 时机 | 解码时直接出小图 | 先解码全图再缩放 |
| 内存 | 与目标尺寸相关 | 先有全图峰值再缩放 |
| 典型场景 | 列表缩略图、头像 | 已解码图的尺寸/比例调整 |
4. 多处理器链式组合
Kingfisher 支持将多个 ImageProcessor 串联成一条管线,按顺序执行:前一个的输出作为后一个的输入(.image(...))[10]。
组合方式:通过 append(another:) 或 |> 运算符(Kingfisher 在 ImageProcessor 扩展中定义 |> 为调用 append(another:)):
// 先模糊,再圆角
let processor = BlurImageProcessor(blurRadius: 4) |> RoundCornerImageProcessor(cornerRadius: 20)
imageView.kf.setImage(with: url, options: [.processor(processor)])
组合后的 identifier:"\(p1.identifier)|>\(p2.identifier)",用于缓存键,保证「同一 URL + 同一处理器链」唯一对应一条缓存。
链式执行语义(伪代码):
函数 GeneralProcessor.process(item, options):
image1 = self.process(item, options)
若 image1 为 nil: 返回 nil
返回 another.process(.image(image1), options)
因此,若链中第一个 Processor 能处理 .data(如 DefaultImageProcessor 或 DownsamplingImageProcessor),后续 Processor 将始终收到 .image(...)。
三、解码、缓存与处理器的协同
1. 检索流程与缓存键
Kingfisher 的检索顺序可概括为 [2][3]:
- 使用 cacheKey + processorIdentifier 查内存缓存;
- 若未命中,查磁盘缓存(同样 key + processorIdentifier);
- 若仍未命中,通过 ImageDownloader 或 ImageDataProvider 获取 Data;
- 对 Data 执行 ImageProcessor 管线,得到 Image;
- 将结果写入内存与磁盘缓存,并交给视图或完成回调。
缓存键:缓存的唯一标识是 cacheKey + processorIdentifier(DefaultImageProcessor 的 identifier 为空字符串)。因此:
- 同一 URL,不同 Processor(或不同链)会得到不同缓存条目;
- 原图(DefaultImageProcessor)与下采样/圆角等版本可并存;
- 判断或读取缓存时若请求中指定了非 Default 的 Processor,需传入相同
processorIdentifier,例如:cache.isCached(forKey: cacheKey, processorIdentifier: processor.identifier)、cache.retrieveImage(forKey: cacheKey, options: [.processor(processor)], ...)。
flowchart TD
A[请求: URL + Processor] --> B[构造 cacheKey + processorIdentifier]
B --> C{内存缓存?}
C -->|命中| D[返回 Image]
C -->|未命中| E{磁盘缓存?}
E -->|命中| F[解码/反序列化]
F --> D
E -->|未命中| G[下载 / Provider]
G --> H[Processor 管线]
H --> I[写内存+磁盘]
I --> D
2. CacheSerializer 与磁盘格式
CacheSerializer 负责「Image ↔ Data」在磁盘缓存中的序列化与反序列化 [10]:
-
存储:
data(with:original:),将当前要缓存的 Image 转为 Data(可结合 original Data 决定格式); -
读取:
image(with:options:),将磁盘上的 Data 转回 Image。
调用时机(便于理解与扩展):
- Processor.process:① 网络下载成功或 ImageDataProvider 返回 Data 后,将 Data 加工为 Image;② 从磁盘读取到原始 Data 后,先经 CacheSerializer 反序列化为 Image,再经 Processor 处理(若请求中指定了 Processor)。因此磁盘命中「已处理图」时直接返回,命中「原图」时会再走一次 Processor。
- CacheSerializer.image:从磁盘读取到 Data 后,用于将 Data 反序列化为 Image。
- CacheSerializer.data:需要写入磁盘时,将 Image 序列化为 Data 再落盘。
默认行为:尽量保持原始数据格式(如 JPEG 仍存为 JPEG)。但当使用 RoundCornerImageProcessor 等会引入透明通道的处理器时,若原图是 JPEG(无透明通道),直接按 JPEG 存会丢失圆角透明区域。此时可指定 FormatIndicatedCacheSerializer.png,强制以 PNG 缓存处理后的图像:
imageView.kf.setImage(with: url,
options: [.processor(RoundCornerImageProcessor(cornerRadius: 20)),
.cacheSerializer(FormatIndicatedCacheSerializer.png)])
3. 内置 Processor 一览
| Processor | 输入偏好 | 功能 |
|---|---|---|
| DefaultImageProcessor | Data / Image | Data→Image 解码,或 Image 按 scale 缩放 |
| DownsamplingImageProcessor | Data | 解码时下采样,限制最大尺寸 |
| ResizingImageProcessor | Image | 按 referenceSize + ContentMode 缩放 |
| RoundCornerImageProcessor | Image | 圆角(可指定角、背景色、目标尺寸) |
| CroppingImageProcessor | Image | 按 size + anchor 裁剪 |
| BlurImageProcessor | Image | 高斯模糊(Accelerate) |
| TintImageProcessor / OverlayImageProcessor | Image | 着色 / 叠色 |
| ColorControlsProcessor / BlackWhiteProcessor | Image | 亮度对比度饱和度 / 黑白 |
| BorderImageProcessor | Image | 加边框 |
| BlendImageProcessor (iOS) / CompositingImageProcessor (macOS) | Image | 混合模式 |
4. 应用场景与选型
| 场景 | 推荐 Processor | 说明 |
|---|---|---|
| 列表/表格缩略图 | DownsamplingImageProcessor(size:) | 从 Data 直接下采样,控制内存;size 取 cell 或目标尺寸 |
| 头像/圆角 | RoundCornerImageProcessor | 可配合 .png serializer 保留透明圆角 |
| 占位/毛玻璃 | BlurImageProcessor | 基于 Accelerate 的高斯模糊 |
| 统一尺寸且需等比 | ResizingImageProcessor(referenceSize, mode: .aspectFit) | 对已解码图做缩放 |
| 多步效果 | 链式:e.g. Blur |> RoundCorner | 顺序决定最终效果与缓存键 |
RoundCornerImageProcessor 指定圆角:除四角统一圆角外,可指定部分角,如仅左上与右下:RoundCornerImageProcessor(cornerRadius: 20, roundingCorners: [.topLeft, .bottomRight])。
四、类结构图分析
1. 核心类总览
Kingfisher 的类可按职责分为:入口与协调、加载、缓存、处理管线、视图扩展 五类。下表给出核心类/协议及其职责。
| 模块 | 核心类 / 协议 | 职责简述 | |
|---|---|---|---|
| 协调 | KingfisherManager |
统一入口:协调 ImageDownloader、ImageCache、ImageProcessor 管线,执行「查缓存 → 下载/Provider → 处理 → 写缓存 → 回调」 | |
| 加载 |
ImageDataProvider (协议) |
定义数据来源接口:根据 URL 或资源标识返回 Data(如 Base64ImageDataProvider、LocalFileImageDataProvider) |
|
ImageDownloader |
默认网络加载:基于 URLSession 下载,支持并发、取消、RequestModifier、SessionDelegate | ||
ImageDownloaderOperation |
单次下载任务,封装 URLSessionTask | ||
| 缓存 | ImageCache |
内存 + 磁盘二级缓存,提供 retrieve/store/remove,key 含 cacheKey + processorIdentifier | |
MemoryStorage / DiskStorage
|
5.0+ 内存层、磁盘层具体实现,可配置 count/cost 限制与过期策略 | ||
| 处理管线 |
ImageProcessor (协议) |
定义 process(item:options:) -> KFCrossPlatformImage?,输入为 ImageProcessItem(.data / .image) |
|
ImageProcessItem (枚举) |
双态输入:.data(Data) 或 .image(KFCrossPlatformImage),统一「仅解码」「仅变换」「解码+变换」 |
||
DefaultImageProcessor / DownsamplingImageProcessor / RoundCornerImageProcessor 等 |
内置 Processor 实现,支持 ` | >` 链式组合 | |
CacheSerializer (协议) |
磁盘格式:Image ↔ Data 序列化/反序列化,如 FormatIndicatedCacheSerializer.png
|
||
| 视图 |
KingfisherWrapper + ImageView.kf
|
为 UIImageView/NSImageView 等提供 kf.setImage(with:options:...)、kf.cancelDownloadTask()
|
|
KFImage (SwiftUI) |
SwiftUI 图片组件,支持 URL、Processor、progressiveJPEG 等 | ||
ImagePrefetcher |
预取多张图片,可配合 UICollectionView 的 prefetch |
2. 模块划分与依赖关系
下图从「模块」维度表示各层之间的依赖方向:视图扩展与 Prefetcher 依赖 KingfisherManager,Manager 依赖 Downloader/Cache,处理管线在 Manager 内执行(Processor 链与 CacheSerializer 参与缓存键与磁盘格式)。
flowchart TB
subgraph 视图层
V1[ImageView.kf / KFImage]
V2[ImagePrefetcher]
end
subgraph 协调层
M[KingfisherManager]
end
subgraph 加载层
L[ImageDownloader]
P[ImageDataProvider 实现]
end
subgraph 缓存层
C[ImageCache]
end
subgraph 处理管线层
IP[ImageProcessor 实现]
CS[CacheSerializer]
end
V1 --> M
V2 --> M
M --> L
M --> P
M --> C
M --> IP
M --> CS
3. 加载与缓存类结构
ImageDownloader 负责从网络获取 Data;ImageDataProvider 可提供本地或自定义 Data;ImageCache 负责内存与磁盘的读写。KingfisherManager 持有 cache 与 downloader,在单次请求中先查缓存(key = cacheKey + processorIdentifier),未命中再通过 downloader 或 provider 取数据,经 Processor 管线后写回缓存。
classDiagram
class KingfisherManager {
-cache: ImageCache
-downloader: ImageDownloader
+retrieveImage(with:options:progressBlock:completionHandler:)
-loadAndCacheImage(source:options:completionHandler:)
}
class ImageCache {
-memoryStorage: MemoryStorage
-diskStorage: DiskStorage
+retrieveImage(forKey:options:callbackQueue:completionHandler:)
+store(_:forKey:options:toDisk:completionHandler:)
+removeImage(forKey:fromMemory:fromDisk:completionHandler:)
}
class ImageDownloader {
-session: URLSession
-downloadQueue: OperationQueue
+downloadImage(with:options:completionHandler:)
}
class ImageDataProvider {
<<protocol>>
+data(handler:)
+cacheKey
}
KingfisherManager --> ImageCache : 使用
KingfisherManager --> ImageDownloader : 使用
KingfisherManager ..> ImageDataProvider : 支持 Source.provider
-
KingfisherManager:对外通过
retrieveImage(with:...)接收Source(.network(URL) 或 .provider(ImageDataProvider)),先查ImageCache(key 含 processorIdentifier),未命中则调 downloader 或 provider 取 Data,再跑 Processor 管线并写回缓存。 -
ImageCache:5.0+ 将内存与磁盘拆为
MemoryStorage/DiskStorage,可配置 count/cost、过期时间;存储时由CacheSerializer决定 Image → Data 的格式(如 PNG 保留圆角透明)。 -
ImageDownloader:基于 URLSession,单次下载封装为
ImageDownloaderOperation,支持并发数、超时、RequestModifier;与 Provider 一起构成「数据来源」的两种方式。
4. 处理管线与 Processor 类结构
ImageProcessor 协议是图层处理的核心:输入为 ImageProcessItem(.data 或 .image),输出为 KFCrossPlatformImage。Manager 在「取得 Data 后」按 options 中的 processor(或链)依次执行;链的 identifier 拼接后参与缓存键,实现「同一 URL + 不同 Processor」对应不同缓存条目。
classDiagram
class KingfisherManager {
-runProcessors(_:data:options:)
}
class ImageProcessItem {
<<enumeration>>
+image(KFCrossPlatformImage)
+data(Data)
}
class ImageProcessor {
<<protocol>>
+identifier: String
+process(item: ImageProcessItem, options: KingfisherParsedOptionsInfo): KFCrossPlatformImage?
}
class DefaultImageProcessor {
+process(item:options:)
}
class DownsamplingImageProcessor {
+size: CGSize
+process(item:options:)
}
class RoundCornerImageProcessor {
+cornerRadius: CGFloat
+process(item:options:)
}
class ImageProcessorGroup {
-processors: [ImageProcessor]
+append(another:)
+identifier
}
class CacheSerializer {
<<protocol>>
+data(with:original:)
+image(with:options:)
}
ImageProcessItem --> ImageProcessor : 输入
ImageProcessor <|.. DefaultImageProcessor : 实现
ImageProcessor <|.. DownsamplingImageProcessor : 实现
ImageProcessor <|.. RoundCornerImageProcessor : 实现
ImageProcessor <|.. ImageProcessorGroup : 链式组合
KingfisherManager ..> ImageProcessor : 执行管线
KingfisherManager ..> CacheSerializer : 磁盘序列化
-
ImageProcessItem:双态设计使同一管线既可处理「Data → Image」(解码/下采样),也可处理「Image → Image」(圆角、模糊、缩放),或混合链式处理;收到
.data的 Processor 可先通过DefaultImageProcessor.default |> self解码再变换。 -
ImageProcessor 链:通过
append(another:)或|>组合,链的 identifier 为各子 Processor identifier 用"|>"拼接,参与缓存键;执行时前一个输出作为后一个的.image(...)输入。 -
CacheSerializer:磁盘存储时由
data(with:original:)将 Image 转为 Data,读取时由image(with:options:)反序列化;圆角等带透明通道的结果可选用FormatIndicatedCacheSerializer.png避免 JPEG 丢失透明。
5. View 扩展与调用链
视图扩展(如 ImageView.kf、SwiftUI 的 KFImage)是业务最常接触的入口:内部将 Resource(URL 或 ImageDataProvider)、placeholder、options 交给 KingfisherManager,并把返回的 DownloadTask 与 view 关联,以便在复用时取消。
sequenceDiagram
participant V as ImageView
participant KF as ImageView.kf
participant M as KingfisherManager
participant C as ImageCache
participant D as ImageDownloader
V->>KF: setImage(with: url, options: [.processor(...)])
KF->>KF: cancelDownloadTask()
KF->>M: retrieveImage(with: .network(url), options: ...)
M->>C: retrieveImage(forKey: cacheKey+processorIdentifier)
alt 缓存命中
C-->>M: image (memory/disk)
M-->>KF: completion(image, .memory/.disk)
else 未命中
M->>D: downloadImage(with: url, ...)
D-->>M: data
M->>M: Processor 管线处理
M->>C: store(image, forKey: ...)
M-->>KF: completion(image, .none)
end
KF->>V: imageView.image = image
-
kf.setImage(with: placeholder: options: progressBlock: completionHandler:):先对当前 view 取消未完成的
DownloadTask,再调KingfisherManager.shared.retrieveImage(with: source, options: options, ...);在 completion 中把得到的 image 赋给imageView.image(并可选执行 transition)。 - kf.cancelDownloadTask():取消与该 view 绑定的任务,避免 cell 复用时旧请求覆盖新图。
-
KFImage (SwiftUI):通过
KFImage传入 url、processor、placeholder 等,内部同样走 KingfisherManager,支持 progressiveJPEG(8.3+)等选项。
将上述「核心类总览」「模块依赖」「加载与缓存类图」「Processor 与管线类图」「View 调用链」串联起来,即可形成对 Kingfisher 类结构图 的完整分析:入口在视图扩展(kf / KFImage),核心协调在 KingfisherManager,加载(Downloader/Provider)、缓存(ImageCache + CacheSerializer)、处理(ImageProcessor + ImageProcessItem)均为协议导向的可插拔设计,便于扩展与测试。
五、与系统及业界实践的衔接
1. Apple 图像与图形最佳实践
Apple 在 WWDC 2018「Image and Graphics Best Practices」[8] 中强调:
- 在后台线程解码与下采样,避免主线程卡顿;
- 解码时即做下采样,使解码后缓冲与显示尺寸匹配,降低内存峰值;
- 预取:在列表等场景提前准备即将显示的图像。
Kingfisher 的 DownsamplingImageProcessor 直接对应「解码时下采样」;处理管线在 KingfisherManager 的队列中执行,满足「后台处理」;配合 ImagePrefetcher 与 UICollectionViewDataSourcePrefetching 等可实现预取 [10]。与 SDWebImage 类似,其设计与此类最佳实践一致。
2. 与 SDWebImage 的对比
| 维度 | Kingfisher | SDWebImage |
|---|---|---|
| 语言 | 纯 Swift | Objective-C 为主,Swift 接口 |
| 处理抽象 | ImageProcessor + ImageProcessItem | SDImageTransformer |
| 输入类型 | .image / .data 双态 | 一般为已解码 Image |
| 下采样 | DownsamplingImageProcessor(Data→Image) | 解码管线内缩略图/limitBytes |
| 链式组合 | append / |>,identifier 拼接 | SDImagePipelineTransformer 数组 |
| 缓存键 | cacheKey + processorIdentifier | 含 transformer 信息 |
| 渐进式 | 8.3+ KFImage progressiveJPEG | Progressive Coder 体系 |
二者都遵循「解码/下采样 + 变换 + 缓存」的管线思想,Kingfisher 通过 ImageProcessItem 将「解码」与「变换」统一进同一协议,便于从 Data 直接到最终 Image 的一体化处理。
3. 动图加载(GIF)与 AnimatedImageView
Kingfisher 加载 GIF 的两种方式:UIImageView 与 AnimatedImageView(继承自 UIImageView),调用方式相同,内部行为不同 [12]。
-
UIImageView:
shouldPreloadAllAnimation()扩展返回true,即 preloadAllAnimationData 被设为 true,GIF 会先解码为所有帧的 UIImage 数组,再通过UIImage.animatedImage(with:duration:)展示。适合帧数少的动图。 -
AnimatedImageView:重写
shouldPreloadAllAnimation()返回false,不预加载全部帧;通过关联的 CGImageSource 与 Animator 按需解码(默认仅预加载前若干帧),用 CADisplayLink 在每帧刷新时更新layer.contents(重写display(_ layer:))。更省内存,CPU 略高。
AnimatedImageView 独有:runLoopMode、backgroundDecode、framePreloadCount、autoPlayAnimatedImage、repeatCount 等。若需在列表或详情中播放 GIF 且控制内存,建议使用 AnimatedImageView。
六、设计模式与编程思想
1. 设计模式应用
Kingfisher 在架构上大量运用经典设计模式,与纯 Swift、协议导向的风格结合,使扩展与维护成本可控。
| 模式 | 在 Kingfisher 中的体现 | 作用 |
|---|---|---|
| 外观 / 门面(Facade) |
KingfisherManager 对外提供 retrieveImage(with:options:progressBlock:completionHandler:),内部协调 ImageDownloader、ImageCache、ImageProcessor 管线,调用方无需关心多级缓存与处理顺序 |
简化使用、隐藏复杂度 |
| 策略(Strategy) | ImageProcessor、CacheSerializer、ImageDataProvider 均为协议,多种实现可替换(RoundCorner、Downsampling、FormatIndicatedCacheSerializer 等),通过 KingfisherOptionsInfo 传入 | 算法/行为可插拔,易扩展新处理与存储格式 |
| 责任链 / 管道(Chain of Responsibility / Pipeline) |
ImageProcessor 通过 append(another:) 或 |> 串联成管线;ImageProcessItem 双态(.data / .image)使「解码 → 变换」在同一链中顺序执行 |
多步处理顺序清晰,便于组合与复用 |
| 单例 + 共享依赖(Singleton) |
KingfisherManager.shared、ImageCache.default、ImageDownloader.default 提供默认实例,同时 retrieveImage 等 API 支持传入自定义 cache、downloader,打破单例绑定 |
全局统一入口,又保留可测试性与多实例能力 |
| 观察者 / 回调(Observer / Callback) | 通过 progressBlock、completionHandler 闭包通知进度与结果;Swift 并发下也可用 async/await | 异步结果与 UI 解耦 |
| 组合 / 装饰(Composite) | 多个 ImageProcessor 通过 |> 组合成新 Processor,其 identifier 为子 Processor 的 identifier 拼接,对外仍满足同一 ImageProcessor 协议 |
链式处理器可当作单一策略使用,参与缓存键一致 |
类图关系(概念层):
classDiagram
class KingfisherManager {
-cache: ImageCache
-downloader: ImageDownloader
+retrieveImage(with:options:progressBlock:completionHandler:)
}
class ImageCache {
+retrieveImage(forKey:options:callbackQueue:completionHandler:)
+store(_:forKey:options:toDisk:completionHandler:)
}
class ImageDownloader {
+downloadImage(with:options:completionHandler:)
}
class ImageProcessor {
<<protocol>>
+identifier: String
+process(item: ImageProcessItem, options:): KFCrossPlatformImage?
}
class ImageDataProvider {
<<protocol>>
+data(handler:)
+cacheKey
}
KingfisherManager --> ImageCache : 使用
KingfisherManager --> ImageDownloader : 使用
KingfisherManager ..> ImageProcessor : 处理时选用
KingfisherManager ..> ImageDataProvider : Source.provider
2. 编程思想精华
Kingfisher 的编程思想可提炼为以下几点,对理解与模仿其设计很有帮助。
2.1 协议导向与「可替换实现」
- ImageProcessor、CacheSerializer、ImageDataProvider 均以协议呈现,具体实现可替换、可组合。
- 新增一种图像处理或一种磁盘格式,只需实现对应协议并通过 options 传入(如
.processor(...)、.cacheSerializer(...)),无需改动 KingfisherManager 核心流程。这体现了开闭原则:对扩展开放,对修改关闭。
2.2 管线化与单一职责
- 把「从 Source 到屏幕」拆成:获取数据(Downloader/Provider)→ Processor 管线(解码/下采样 + 变换)→ 缓存 → 展示,每一步只做一件事。
- Processor 只关心 ImageProcessItem → Image,Cache 只关心存储与查找,Downloader 只关心网络 Data。单一职责使每块可独立测试、优化和扩展;管线化则使数据流清晰,便于加日志与监控。
2.3 双态输入与「解码+变换」统一
-
ImageProcessItem 的
.data/.image双态设计,使同一 ImageProcessor 协议既能表达「Data → Image」(如 Default、Downsampling),也能表达「Image → Image」(如 RoundCorner、Blur),还能通过链式组合在一次管线中完成解码与多步变换。 - 避免「解码器」与「变换器」两套抽象,降低概念数量,便于链式组合与缓存键一致(整条链一个 identifier 串)。
2.4 缓存键与「同一资源多形态」
- 通过 cacheKey + processorIdentifier 的设计,同一 URL 可以对应「原图」「下采样图」「圆角图」等多条缓存,避免重复下载,又满足不同场景对尺寸/形态的需求。这体现了用键设计表达业务差异的思想。
2.5 后台处理与主线程回调
- 下载、Processor 管线、磁盘 I/O 均在后台队列执行,completionHandler 通过 CallbackQueue.mainAsync 等派发到主线程,兼顾性能与 UI 安全。这是移动端异步加载库的通用范式:重活放后台,结果回主线程。
2.6 取消与生命周期绑定
- 视图扩展(如
ImageView.kf)会把「当前正在进行的 DownloadTask」与 view 绑定,当对同一 view 发起新请求时先取消旧任务,避免错位和浪费。这体现了生命周期与请求绑定的思想,在列表 cell 复用时尤为重要。
2.7 配置通过 Options 透传
- 不通过全局单例属性堆砌配置,而是通过 KingfisherOptionsInfo(如
.processor、.cacheSerializer、.callbackQueue)在单次请求中传入,使「同一 App 内不同页面/模块」可使用不同 Processor 与缓存策略,且易于单元测试时注入 mock。
Kingfisher 编程思想精华一览:
| 思想 | 在框架中的体现 |
|---|---|
| 协议导向、可替换 | ImageProcessor / CacheSerializer / ImageDataProvider 协议化,新处理、新格式仅需实现协议并通过 options 传入 |
| 管线化、单一职责 | 获取数据 → Processor 管线 → 缓存 → 展示,每步职责单一,便于扩展与测试 |
| 双态输入、解码+变换统一 | ImageProcessItem(.data / .image) + 链式 Processor,一条管线完成解码与多步变换,identifier 参与缓存键 |
| 键设计表达多形态 | 同一 URL 通过 cacheKey + processorIdentifier 支持原图、下采样图、圆角图等多条缓存 |
| 后台处理、主线程回调 | 重 CPU/IO 在后台队列,completion 回主线程(CallbackQueue),兼顾性能与 UI 安全 |
| 生命周期绑定取消 | View 与 DownloadTask 绑定,新请求自动取消旧请求,避免列表错位 |
| Options 透传配置 | 单次请求级 options(processor、cacheSerializer、callbackQueue 等),避免全局状态,利于多策略并存与测试注入 |
七、使用示例与最佳实践
1. 基础加载与圆角
let processor = RoundCornerImageProcessor(cornerRadius: 20)
imageView.kf.setImage(with: url, options: [.processor(processor)])
2. 列表缩略图(下采样)
let size = imageView.bounds.size
let processor = DownsamplingImageProcessor(size: size)
imageView.kf.setImage(with: url, options: [.processor(processor)])
// 注意:size 不可为 .zero
3. 多处理器链与强制 PNG 缓存
let processor = BlurImageProcessor(blurRadius: 4) |> RoundCornerImageProcessor(cornerRadius: 20)
imageView.kf.setImage(with: url, options: [
.processor(processor),
.cacheSerializer(FormatIndicatedCacheSerializer.png)
])
4. 自定义 Processor(仅做示意)
struct MyProcessor: ImageProcessor {
let identifier = "com.example.myprocessor"
func process(item: ImageProcessItem, options: KingfisherParsedOptionsInfo) -> KFCrossPlatformImage? {
switch item {
case .image(let image): return image // 或对 image 做变换
case .data(let data): return DefaultImageProcessor.default.process(item: item, options: options)
}
}
}
5. 预取与列表
// 配合 UICollectionViewDataSourcePrefetching
func collectionView(_ collectionView: UICollectionView, prefetchItemsAt indexPaths: [IndexPath]) {
let urls = indexPaths.compactMap { URL(string: model(at: $0).imageURL) }
ImagePrefetcher(urls: urls).start()
}
6. Cell 完整示例(复用、下采样、进度与完成回调)
列表 Cell 中需在 prepareForReuse 中取消任务并清空,在 configure 中按目标尺寸下采样并可选显示进度。
class PhotoCell: UITableViewCell {
static let reuseId = "PhotoCell"
@IBOutlet weak var photoImageView: UIImageView!
@IBOutlet weak var progressView: UIProgressView!
override func prepareForReuse() {
super.prepareForReuse()
photoImageView.kf.cancelDownloadTask()
photoImageView.image = nil
progressView.progress = 0
progressView.isHidden = true
}
func configure(with url: URL) {
let size = photoImageView.bounds.size
let processor = DownsamplingImageProcessor(size: size.isEmpty ? CGSize(width: 120, height: 120) : size)
photoImageView.kf.setImage(
with: url,
placeholder: UIImage(named: "placeholder"),
options: [.processor(processor), .scaleFactor(UIScreen.main.scale)],
progressBlock: { [weak self] received, total in
guard let self = self, total > 0 else { return }
DispatchQueue.main.async {
self.progressView.isHidden = false
self.progressView.progress = Float(received) / Float(total)
}
},
completionHandler: { [weak self] result in
DispatchQueue.main.async {
self?.progressView.isHidden = true
if case .failure = result { /* 可设置失败占位图 */ }
}
}
)
}
}
7. UIButton 设置网络图片
为 UIButton 的不同 state 设置网络图片,可配合 Processor 与完成回调。
// 设置 normal / highlighted 等状态的图片
button.kf.setImage(with: url, for: .normal, placeholder: UIImage(named: "btn_placeholder"))
button.kf.setImage(with: highlightedURL, for: .highlighted)
button.kf.setBackgroundImage(with: backgroundURL, for: .normal)
// 带圆角与完成回调
let processor = RoundCornerImageProcessor(cornerRadius: 8)
button.kf.setImage(
with: url,
for: .normal,
placeholder: nil,
options: [.processor(processor), .cacheSerializer(FormatIndicatedCacheSerializer.png)],
completionHandler: { result in
if case .failure = result { print("加载失败") }
}
)
8. 占位图、进度与过渡动画
使用占位图、下载进度条,并在图片加载完成后执行淡入等过渡动画。
imageView.kf.setImage(
with: url,
placeholder: UIImage(named: "placeholder"),
options: [
.transition(ImageTransition.fade(0.3)),
.retryFailed
],
progressBlock: { [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
}
},
completionHandler: { [weak progressView] result in
DispatchQueue.main.async {
progressView?.isHidden = true
if case .failure = result { /* 可显示失败占位或提示 */ }
}
}
)
9. 自定义缓存键与请求修饰(RequestModifier)
同一 URL 在不同业务下需要不同缓存键时,可通过 KingfisherOptionsInfo 传入自定义 cacheKey;需要鉴权或自定义 Header 时使用 ImageDownloadRequestModifier。
// 自定义缓存键:列表用 thumb key、详情用原图 key
let listResource = ImageResource(downloadURL: url, cacheKey: "list_\(url.absoluteString)")
let detailResource = ImageResource(downloadURL: url, cacheKey: "detail_\(url.absoluteString)")
listImageView.kf.setImage(with: listResource, options: [.processor(DownsamplingImageProcessor(size: thumbSize))])
detailImageView.kf.setImage(with: detailResource)
// 请求修饰:Header、Token、超时
struct AuthModifier: ImageDownloadRequestModifier {
let token: String
func modified(for request: URLRequest) -> URLRequest? {
var r = request
r.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
r.setValue("image/webp,image/*,*/*;q=0.8", forHTTPHeaderField: "Accept")
return r
}
}
imageView.kf.setImage(with: url, options: [.requestModifier(AuthModifier(token: userToken))])
10. 缓存查询与手动存储
不经过视图加载流程,直接使用 ImageCache 查询、存储或移除缓存。
let cache = ImageCache.default
let key = url.absoluteString // 或自定义 cacheKey(与 Processor 组合时由框架自动拼接 processorIdentifier)
// 查询是否已缓存
cache.imageCachedType(forKey: key) { result in
switch result {
case .success(let cached):
switch cached {
case .none: print("未缓存")
case .memory: print("在内存")
case .disk: print("在磁盘")
}
case .failure: break
}
}
// 从缓存读取(不触发下载)
cache.retrieveImage(forKey: key, options: nil) { result in
switch result {
case .success(let value):
if let image = value.image { imageView.image = image }
case .failure: break
}
}
// 手动写入缓存(如本地生成或从相册来的图)
cache.store(image, forKey: key, options: nil, toDisk: true) { _ in }
11. 自定义 Processor 完整示例(加边框)
实现 ImageProcessor 协议,对已解码图像做自定义绘制(如加灰色边框)。
struct GrayBorderProcessor: ImageProcessor {
let identifier = "com.example.grayborder(\(borderWidth))"
let borderWidth: CGFloat
init(borderWidth: CGFloat = 2) { self.borderWidth = borderWidth }
func process(item: ImageProcessItem, options: KingfisherParsedOptionsInfo) -> KFCrossPlatformImage? {
switch item {
case .image(let image):
let size = image.size
let renderer = UIGraphicsImageRenderer(size: size)
return renderer.image { ctx in
image.draw(at: .zero)
UIColor.gray.setStroke()
let rect = CGRect(origin: .zero, size: size).insetBy(dx: borderWidth/2, dy: borderWidth/2)
ctx.stroke(rect, with: .color(.gray), lineWidth: borderWidth)
}
case .data:
return DefaultImageProcessor.default.process(item: item, options: options)
}
}
}
// 使用
imageView.kf.setImage(with: url, options: [.processor(GrayBorderProcessor(borderWidth: 3))])
12. SwiftUI KFImage 与 async/await
在 SwiftUI 中使用 KFImage,并配合渐进式 JPEG、占位与异步加载。
// 基础用法
KFImage(url)
.placeholder { ProgressView() }
.fade(duration: 0.25)
.resizable()
.aspectRatio(contentMode: .fit)
// 带 Processor 与圆角
KFImage(url)
.setProcessor(RoundCornerImageProcessor(cornerRadius: 12))
.placeholder { Color.gray.opacity(0.2) }
.cacheSerializer(FormatIndicatedCacheSerializer.png)
// 8.3+ 渐进式 JPEG
KFImage(url)
.progressiveJPEG(ImageProgressive(isBlur: true, isFastestScan: true, scanInterval: 0.1))
// 使用 async/await(Kingfisher 提供的异步 API)
Task {
let result = await KingfisherManager.shared.retrieveImage(with: url)
if case .success(let value) = result {
await MainActor.run { imageView.image = value.image }
}
}
13. ImageDataProvider(本地与 Base64)用法
不依赖网络 URL 时,可用 ImageDataProvider 从本地文件或 Base64 字符串加载,并同样走缓存与 Processor 管线。
// 本地文件
let fileURL = Bundle.main.url(forResource: "avatar", withExtension: "jpg")!
let provider = LocalFileImageDataProvider(fileURL: fileURL)
imageView.kf.setImage(with: provider)
// Base64 数据(如接口返回的 data URL)
let base64String = "data:image/png;base64,iVBORw0KGgo..."
if let provider = Base64ImageDataProvider(base64String: base64String, cacheKey: "custom_key") {
imageView.kf.setImage(with: provider, options: [.processor(RoundCornerImageProcessor(cornerRadius: 10))])
}
// 自定义 Provider:从相册、加密存储等获取 Data
struct MyImageDataProvider: ImageDataProvider {
var cacheKey: String { "my_\(id)" }
let id: String
func data(handler: @escaping (Result<Data, Error>) -> Void) {
// 异步获取 Data 后调用 handler(.success(data)) 或 handler(.failure(...))
}
}
imageView.kf.setImage(with: MyImageDataProvider(id: "123"))
14. 其他常用选项速览
| 选项 | 含义 |
|---|---|
.forceRefresh |
跳过缓存,强制重新下载 |
.retryFailed |
对之前失败的 URL 重试 |
.onlyFromCache |
仅从缓存读取,不发起网络请求 |
.backgroundDecode |
在后台队列解码,减少主线程压力 |
.callbackQueue(.mainAsync) |
指定完成回调的派发队列 |
.downloadPriority(1.0) |
下载任务优先级(iOS) |
.scaleFactor(UIScreen.main.scale) |
与 @2x/@3x 匹配,避免模糊 |
.cacheMemoryOnly |
仅写内存缓存,不写磁盘 |
.loadDiskFileSynchronously |
从磁盘加载时是否同步(默认异步) |
imageView.kf.setImage(with: url, options: [.forceRefresh, .retryFailed, .callbackQueue(.mainAsync)])
15. Options 详解(延伸)
-
targetCache / originalCache:默认为
nil时使用ImageCache(name: "default")。targetCache为最终展示图的缓存(含 Processor 处理后的图),originalCache为原始数据的缓存,可用于「列表用处理图、详情用原图」等分离策略。 - transition:图片加载完成后的展示动画;forceTransition 为 true 时即使命中缓存也执行 transition,为 false 时仅在不使用缓存(新下载)时执行。
-
callbackQueue / processingQueue:
callbackQueue可选.mainAsync、.mainCurrentOrAsync(当前线程为主线程则直接执行,否则主线程异步)、.untouch、.dispatch(DispatchQueue),默认多为.mainCurrentOrAsync;processingQueue为 Processor 执行所在队列,默认串行子队列。 -
memoryCacheAccessExtendingExpiration:从内存/磁盘取图时是否延长过期时间,可选
.none(不延长)、.cacheTime(当前时间 + 原过期时长)、.expirationTime(StorageExpiration)(延长到指定时长)。
16. 指示器、Placeholder 与 Transition 类型
-
指示器(Indicator):
imageView.kf.indicatorType可选.none、.activity(UIActivityIndicatorView)、.image(imageData: Data)(GIF 等)、.custom(indicator: Indicator),自定义需实现Indicator协议(startAnimatingView/stopAnimatingView)。 -
Placeholder:除
UIImage外,可实现 Placeholder 协议的自定义 View(如class MyPlaceholder: UIView, Placeholder {}),设置imageView.kf.setImage(with: url, placeholder: myPlaceholderView)。 -
ImageTransition:
none、fade(TimeInterval)、flipFromLeft/Right/Top/Bottom(TimeInterval)、custom(duration:options:animations:completion:)。
17. 缓存配置与清除
内存缓存(cache.memoryStorage.config):totalCostLimit(默认约物理内存 1/4)、countLimit、expiration(默认 300 秒)、cleanInterval(清除过期缓存的时间间隔,仅初始化可设)。单张可设 .memoryCacheExpiration(.never);访问时延长策略用 .memoryCacheAccessExtendingExpiration(.cacheTime)。
磁盘缓存(cache.diskStorage.config):sizeLimit、expiration(默认 7 天)、pathExtension、usesHashedFileName(文件名是否用 key 的 MD5)。超出容量时按最后访问时间排序,删除最旧文件直至低于 sizeLimit 的一半。
清除:cache.clearMemoryCache() / cache.cleanExpiredMemoryCache();cache.clearDiskCache() / cache.cleanExpiredDiskCache();删除指定 key 可用 cache.removeImage(forKey:processorIdentifier:fromMemory:fromDisk:completionHandler:)。获取磁盘占用:cache.calculateDiskStorageSize { result in ... }。
18. ImagePrefetcher 与请求修饰、重定向
ImagePrefetcher:除 start() 外,提供 completionHandler(参数为 [Resource] 的 skipped/failed/completed)与 completionSourceHandler(参数为 [Source]),分别对应用 URL/Resource 初始化与用 Source 初始化的场景;progressBlock / progressSourceBlock 同理。maxConcurrentDownloads 控制并发数。stop() 会取消当前未完成的下载任务,并将剩余未加载项计入「完成回调」的 skipped;若调用 stop 时已全部完成,则不会再次触发完成回调。
请求修饰:通过 AnyModifier 或实现 ImageDownloadRequestModifier 在请求前添加 Header、Token 等,例如 let modifier = AnyModifier { var r = $0; r.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization"); return r },options 中加 .requestModifier(modifier)。超时:ImageDownloader.default.downloadTimeout = 60。重定向:通过 .redirectHandler(AnyRedirectHandler { ... }) 自定义 302 等重定向后的请求。
19. 扩展 WebP 支持(Processor + CacheSerializer)
Kingfisher 默认不包含 WebP 编解码,可借助 Processor 与 CacheSerializer 扩展 [13]。依赖 libwebp 实现 Data ↔ Image 后,定义 WebPProcessor(在 process 中若为 .data 则用 WebP 解码为 Image,若为 .image 则透传)与 WebPCacheSerializer(data(with:original:) 返回 WebP 编码、image(with:options:) 返回 WebP 解码),使用时设置 options: [.processor(WebPProcessor.default), .cacheSerializer(WebPCacheSerializer.default)] 即可对 .webp URL 加载并缓存。
延伸阅读(掘金系列)
以下为同一作者的 Kingfisher 源码解析系列文章,可按需跳转深入阅读(链接与标题保持一致):
| 主题 | 链接 | 内容概要 |
|---|---|---|
| 使用 | Kingfisher源码解析之使用 | Resource/ImageDataProvider、Placeholder、GIF、Indicator、Transition、Processor 概览、缓存与下载配置、预加载、常用 options |
| Options 解释 | Kingfisher源码解析之Options解释 | targetCache/originalCache、downloader、transition/forceTransition、preloadAllAnimationData、callbackQueue/processingQueue、memoryCacheAccessExtendingExpiration |
| 加载流程 | Kingfisher源码解析之加载流程 | setImage 之后发生了什么、图片加载与缓存查找流程 |
| ImageCache | Kingfisher源码解析之ImageCache | MemoryStorage(NSCache、StorageObject、Config)、DiskStorage(FileMeta、removeExpiredValues、removeSizeExceededValues)、缓存读写与清理 |
| 加载动图 | Kingfisher源码解析之加载动图 | UIImageView 与 AnimatedImageView 加载 GIF 的差异、preloadAllAnimationData、CGImageSource、Animator、CADisplayLink、display(_ layer:) |
| Processor 和 CacheSerializer | Kingfisher源码解析之Processor和CacheSerializer | Processor/ImageProcessItem 定义与调用时机、CacheSerializer 调用时机、使用 Processor+CacheSerializer 扩展 WebP |
| ImagePrefetcher | Kingfisher源码解析之ImagePrefetcher | 预加载功能、completionHandler/completionSourceHandler、progressBlock/progressSourceBlock、stop() 行为、Resource 与 Source 两套回调 |
参考文献
[1] Kingfisher. Cheat Sheet. GitHub Wiki.
[2] Kingfisher. Image Manager Structure. studyraid.com / Agent Docs.
[3] Kingfisher. CHANGELOG / Releases — 3.10.0 cache retrieval with ImageProcessor.
[4] Kingfisher. Release 5.0.0. GitHub.
[5] Kingfisher. Release 5.3.0 — Downsampling scale/memory fix.
[6] Kingfisher. Release 7.8.1 — Animated image from disk cache with processor.
[7] Kingfisher. Release 8.3.0 — Progressive JPEG for KFImage.
[8] Apple. Image and Graphics Best Practices. WWDC 2018, Session 219.
[9] Kingfisher. ImageProcessor.swift. GitHub (onevcat/Kingfisher).
[10] Kingfisher. Cheat Sheet — Processor, Cache, Downloader. GitHub Wiki.
[11] Stack Overflow / Kingfisher Issues. DownsamplingImageProcessor size (0,0) and processing failure.