02-研究优秀开源框架@图层处理@iOS | Kingfisher 框架:从使用到原理解析

6 阅读20分钟

📋 目录


一、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.0MemoryStorage / DiskStorage 分离,可缓存原始 Data,完善 KingfisherError,处理管线与缓存键绑定 [4]
下采样修复5.3下采样 scale 与内存表现修复:从原图加载下采样结果时的 scale 与内存问题 [5]
动图与序列化7.8磁盘缓存取回动图时正确使用请求中的 processor [6]
渐进式 JPEG8.3SwiftUI 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

对比小结

维度DownsamplingImageProcessorResizingImageProcessor
输入DataImage(或 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]:

  1. 使用 cacheKey + processorIdentifier内存缓存
  2. 若未命中,查磁盘缓存(同样 key + processorIdentifier);
  3. 若仍未命中,通过 ImageDownloaderImageDataProvider 获取 Data;
  4. 对 Data 执行 ImageProcessor 管线,得到 Image;
  5. 将结果写入内存与磁盘缓存,并交给视图或完成回调。

缓存键:缓存的唯一标识是 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输入偏好功能
DefaultImageProcessorData / ImageData→Image 解码,或 Image 按 scale 缩放
DownsamplingImageProcessorData解码时下采样,限制最大尺寸
ResizingImageProcessorImage按 referenceSize + ContentMode 缩放
RoundCornerImageProcessorImage圆角(可指定角、背景色、目标尺寸)
CroppingImageProcessorImage按 size + anchor 裁剪
BlurImageProcessorImage高斯模糊(Accelerate)
TintImageProcessor / OverlayImageProcessorImage着色 / 叠色
ColorControlsProcessor / BlackWhiteProcessorImage亮度对比度饱和度 / 黑白
BorderImageProcessorImage加边框
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(如 Base64ImageDataProviderLocalFileImageDataProvider
ImageDownloader默认网络加载:基于 URLSession 下载,支持并发、取消、RequestModifier、SessionDelegate
ImageDownloaderOperation单次下载任务,封装 URLSessionTask
缓存ImageCache内存 + 磁盘二级缓存,提供 retrieve/store/remove,key 含 cacheKey + processorIdentifier
MemoryStorage / DiskStorage5.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 的队列中执行,满足「后台处理」;配合 ImagePrefetcherUICollectionViewDataSourcePrefetching 等可实现预取 [10]。与 SDWebImage 类似,其设计与此类最佳实践一致。

2. 与 SDWebImage 的对比

维度KingfisherSDWebImage
语言纯 SwiftObjective-C 为主,Swift 接口
处理抽象ImageProcessor + ImageProcessItemSDImageTransformer
输入类型.image / .data 双态一般为已解码 Image
下采样DownsamplingImageProcessor(Data→Image)解码管线内缩略图/limitBytes
链式组合append / |>,identifier 拼接SDImagePipelineTransformer 数组
缓存键cacheKey + processorIdentifier含 transformer 信息
渐进式8.3+ KFImage progressiveJPEGProgressive Coder 体系

二者都遵循「解码/下采样 + 变换 + 缓存」的管线思想,Kingfisher 通过 ImageProcessItem 将「解码」与「变换」统一进同一协议,便于从 Data 直接到最终 Image 的一体化处理。

3. 动图加载(GIF)与 AnimatedImageView

Kingfisher 加载 GIF 的两种方式:UIImageViewAnimatedImageView(继承自 UIImageView),调用方式相同,内部行为不同 [12]。

  • UIImageViewshouldPreloadAllAnimation() 扩展返回 true,即 preloadAllAnimationData 被设为 true,GIF 会先解码为所有帧的 UIImage 数组,再通过 UIImage.animatedImage(with:duration:) 展示。适合帧数少的动图。
  • AnimatedImageView:重写 shouldPreloadAllAnimation() 返回 false,不预加载全部帧;通过关联的 CGImageSourceAnimator 按需解码(默认仅预加载前若干帧),用 CADisplayLink 在每帧刷新时更新 layer.contents(重写 display(_ layer:))。更省内存,CPU 略高。

AnimatedImageView 独有runLoopModebackgroundDecodeframePreloadCountautoPlayAnimatedImagerepeatCount 等。若需在列表或详情中播放 GIF 且控制内存,建议使用 AnimatedImageView


六、设计模式与编程思想

1. 设计模式应用

Kingfisher 在架构上大量运用经典设计模式,与纯 Swift、协议导向的风格结合,使扩展与维护成本可控。

模式在 Kingfisher 中的体现作用
外观 / 门面(Facade)KingfisherManager 对外提供 retrieveImage(with:options:progressBlock:completionHandler:),内部协调 ImageDownloader、ImageCache、ImageProcessor 管线,调用方无需关心多级缓存与处理顺序简化使用、隐藏复杂度
策略(Strategy)ImageProcessorCacheSerializerImageDataProvider 均为协议,多种实现可替换(RoundCorner、Downsampling、FormatIndicatedCacheSerializer 等),通过 KingfisherOptionsInfo 传入算法/行为可插拔,易扩展新处理与存储格式
责任链 / 管道(Chain of Responsibility / Pipeline)ImageProcessor 通过 append(another:)|> 串联成管线;ImageProcessItem 双态(.data / .image)使「解码 → 变换」在同一链中顺序执行多步处理顺序清晰,便于组合与复用
单例 + 共享依赖(Singleton)KingfisherManager.sharedImageCache.defaultImageDownloader.default 提供默认实例,同时 retrieveImage 等 API 支持传入自定义 cache、downloader,打破单例绑定全局统一入口,又保留可测试性与多实例能力
观察者 / 回调(Observer / Callback)通过 progressBlockcompletionHandler 闭包通知进度与结果;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 协议导向与「可替换实现」

  • ImageProcessorCacheSerializerImageDataProvider 均以协议呈现,具体实现可替换、可组合。
  • 新增一种图像处理或一种磁盘格式,只需实现对应协议并通过 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 / processingQueuecallbackQueue 可选 .mainAsync.mainCurrentOrAsync(当前线程为主线程则直接执行,否则主线程异步)、.untouch.dispatch(DispatchQueue),默认多为 .mainCurrentOrAsyncprocessingQueue 为 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)
  • ImageTransitionnonefade(TimeInterval)flipFromLeft/Right/Top/Bottom(TimeInterval)custom(duration:options:animations:completion:)

17. 缓存配置与清除

内存缓存cache.memoryStorage.config):totalCostLimit(默认约物理内存 1/4)、countLimitexpiration(默认 300 秒)、cleanInterval(清除过期缓存的时间间隔,仅初始化可设)。单张可设 .memoryCacheExpiration(.never);访问时延长策略用 .memoryCacheAccessExtendingExpiration(.cacheTime)

磁盘缓存cache.diskStorage.config):sizeLimitexpiration(默认 7 天)、pathExtensionusesHashedFileName(文件名是否用 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 编解码,可借助 ProcessorCacheSerializer 扩展 [13]。依赖 libwebp 实现 Data ↔ Image 后,定义 WebPProcessor(在 process 中若为 .data 则用 WebP 解码为 Image,若为 .image 则透传)与 WebPCacheSerializerdata(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 之后发生了什么、图片加载与缓存查找流程
ImageCacheKingfisher源码解析之ImageCacheMemoryStorage(NSCache、StorageObject、Config)、DiskStorage(FileMeta、removeExpiredValues、removeSizeExceededValues)、缓存读写与清理
加载动图Kingfisher源码解析之加载动图UIImageView 与 AnimatedImageView 加载 GIF 的差异、preloadAllAnimationData、CGImageSource、Animator、CADisplayLink、display(_ layer:)
Processor 和 CacheSerializerKingfisher源码解析之Processor和CacheSerializerProcessor/ImageProcessItem 定义与调用时机、CacheSerializer 调用时机、使用 Processor+CacheSerializer 扩展 WebP
ImagePrefetcherKingfisher源码解析之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.