Kingfisher 性能优化实战:任务取消、原始图缓存与降采样处理器的完整指南
【免费下载链接】KingfisherA lightweight, pure-Swift library for downloading and caching images from the web.项目地址: https://gitcode.com/GitHub_Trending/ki/Kingfisher
导读
本文是 Kingfisher 官方性能指南(Topic_PerformanceTips.md)的深度展开,聚焦三个高频性能场景:如何取消不再需要的图片下载任务以避免浪费网络与电量、如何在使用图像处理器(Processor)时通过cacheOriginalImage让多版本处理复用同一份原始图、以及如何用DownsamplingImageProcessor对大图进行内存友好的降采样。读完本文,你将掌握这些能力对应的完整 API 用法、底层实现原理与测试验证方式,可直接应用到 UITableView / UICollectionView 的列表图片加载优化中。
一、取消不必要的下载任务(Cancelling Unnecessary Downloading Tasks)
1.1 问题本质:下载任务一旦发起就会跑完
Kingfisher 的下载任务是"发起即执行、执行到完成"的。即便你随后给同一个imageView设置了另一个 URL,前一个下载任务也不会被自动取消,而是会照常下载、解码并写入缓存:
imageView.kf.setImage(with: url1) { result in // `result` 是 `.failure(.imageSettingError(.notCurrentSourceTask))` // 因为下面紧接着又调用了 `setImage`。 // // 但 url1 的下载(与缓存)已经正常完成。 } // 紧接着再次设置 imageView.kf.setImage(with: url2) { result in // `result` 是 `.success` }这里有一个非常容易误读的点:第一个回调返回.failure,并不意味着任务失败了。url1的下载其实成功了,只是由于视图已经被url2接管,Kingfisher 出于安全考虑不会把url1的图片显示到视图上,于是向调用方返回了"任务结果已不是当前期望来源"的错误。
1.2 源码剖析:notCurrentSourceTask是如何产生的
这个错误对应KingfisherError.ImageSettingErrorReason.notCurrentSourceTask,错误码 5002,定义在 KingfisherError.swift:
The resource task is completed, but it is not the one that was expected. This typically occurs when you set another resource on the view without canceling the current ongoing task. The previous task will fail with the
.notCurrentSourceTaskerror when a result is obtained, regardless of whether it was successful or not for that task.
其触发机制在 ImageView+Kingfisher.swift 的完成回调中:每次setImage都会生成一个递增的taskIdentifier,回调返回时会校验issuedIdentifier == self.taskIdentifier;若不相等,说明期间发生了新的设置,即走.notCurrentSourceTask分支。你可以用KingfisherError.isNotCurrentTask(见 KingfisherError.swift)快速判断"旧任务被新任务覆盖"这一常见情况,从而选择忽略该错误。
测试侧也有对应验证,见 ImageViewExtensionTests.swift:在连续setImage后,旧任务回调断言返回的是带url1来源的.notCurrentSourceTask错误。
1.3 主动取消:cancelDownloadTask()
既然任务不会自动取消,当你能确定url1的图片不再被需要时,就应该在发起新任务前主动取消它:
imageView.kf.setImage(with: url1) { result in // `result` 是 `.failure(.requestError(.taskCancelled))` // 此时下载任务已被取消。 } imageView.kf.cancelDownloadTask() imageView.kf.setImage(with: url2) { result in // `result` 是 `.success` }从源码看,cancelDownloadTask的实现非常轻量——本质是调用底层imageTask?.cancel():
// Sources/Extensions/ImageView+Kingfisher.swift public func cancelDownloadTask() { imageTask?.cancel() }该 API 并非UIImageView独有,而是通过协议/包装统一提供:
- ImageView+Kingfisher.swift
- HasImageComponent+Kingfisher.swift
- CPListItem+Kingfisher.swift
- NSTextAttachment+Kingfisher.swift
注意与CommonTasks_Downloader中的行为保持一致:cancelDownloadTask()取消的是当前下载任务,而非历史任务。
1.4 实战场景:列表快速滚动时的资源回收
这个技巧在 TableView / CollectionView 中尤其有效。用户快速滚动时,大量 cell 的图片下载任务会被并发发起,其中很多 cell 转瞬即逝、图片永远不会被看到。可以在didEndDisplaying代理方法中取消已离开可视区域的 cell 的下载任务:
func collectionView( _ collectionView: UICollectionView, didEndDisplaying cell: UICollectionViewCell, forItemAt indexPath: IndexPath) { // 当 cell 消失时,取消其尚未完成的下载任务 cell.imageView.kf.cancelDownloadTask() }这样能显著减少网络流量、CPU 解码开销、内存占用与电池消耗。值得强调的是,是否取消是一个权衡:如果url1的图很可能再次被展示(比如用户上滑回看),那么让它"跑完并缓存"反而是划算的——这正是第 1.1 节默认行为的设计初衷。
二、使用处理器时缓存原始图片(Cache Original Image When Using a Processor)
2.1 适用场景
如果你有以下两类需求之一,就应该考虑.cacheOriginalImage选项:
- 对同一张图使用不同的处理器,以得到多个不同版本(缩略图、圆角图、模糊图等);
- 对一张图应用非默认处理器后,将来还需要展示它的原始版本。
.cacheOriginalImage的作用是:在缓存处理结果的同时,把下载得到的原始图片也一并存入缓存:
let p1 = MyProcessor() imageView.kf.setImage(with: url, options: [.processor(p1), .cacheOriginalImage])此时p1处理后的图与原始图都被缓存了。之后若换用另一个处理器:
let p2 = AnotherProcessor() imageView.kf.setImage(with: url, options: [.processor(p2)])Kingfisher 会发现"该 URL 的原始图已在缓存中",于是不再重新下载,而是直接复用原始图并就地应用p2。
2.2 源码剖析:条件、存储与复用的完整链路
cacheOriginalImage选项定义在 KingfisherOptionsInfo.swift,默认值为false(KingfisherParsedOptionsInfo中public var cacheOriginalImage = false,测试见 KingfisherOptionsInfoTests.swift)。
写入链路(KingfisherManager.swift):
let needToCacheOriginalImage = options.cacheOriginalImage && options.processor != DefaultImageProcessor.default两个关键细节:
- 只有
cacheOriginalImage为true且当前处理器不是默认处理器时,才会触发原始图缓存——若直接用默认处理器,处理结果即原始图,无需重复存储; - 原始图通过
originalCache.storeToDisk(...)仅写入磁盘缓存(storeToDisk而非内存缓存),使用的 key 与原始资源相同,processorIdentifier固定为DefaultImageProcessor.default.identifier,即不带任何处理器标识。因此同一 URL 的原始图与各处理版本在缓存中通过"key + processorIdentifier"天然区分。
读取/复用链路(KingfisherManager.swift):当请求的已处理图未命中缓存时,Kingfisher 会回退检查原始图:
// 2. Check whether the original image exists. If so, get it, process it, save to storage and return. let originalCache = options.originalCache ?? targetCache若原始图存在,Kingfisher 会将其取出、应用当前处理器,并把处理结果写回缓存后返回。这就是"换处理器不重复下载"的底层保证。
补充说明:如果想为原始图指定独立的缓存实例(避免与处理结果混淆),可以使用.originalCache(_:)选项(参见 KingfisherOptionsInfo.swift 的说明)。测试 KingfisherManagerTests.swift 即验证了.originalCache(originalCache)下的原始图存储行为。
三、对超高分辨率图片进行降采样(Downsampling the Excessively High Resolution Images)
3.1 问题背景与推荐组合
在 TableView / CollectionView 的 cell 中展示大图时,使用更小的缩略图可以同时降低下载耗时与内存占用。若服务端不提供缩略图,DownsamplingImageProcessor就是首选方案——它在图片加载进内存之前就完成降采样,直接优化内存峰值:
imageView.kf.setImage( with: resource, placeholder: placeholderImage, options: [ .processor(DownsamplingImageProcessor(size: imageView.size)), .scaleFactor(UIScreen.main.scale), .cacheOriginalImage ])这三项配置各司其职:
.processor(DownsamplingImageProcessor(size: imageView.size)):把高分辨率大图降采样到目标显示尺寸;.scaleFactor(UIScreen.main.scale):按屏幕像素密度(2x / 3x)换算,保证显示清晰;见 KingfisherOptionsInfo.swift 对scaleFactor的说明——应指定图片自身的 scale 而非屏幕 scale,否则可能得到 scale 为 1.0 的图;.cacheOriginalImage:把原始高清图也缓存起来,避免未来需要原图或换处理器时重新下载。
3.2 源码剖析:为什么降采样更省内存
DownsamplingImageProcessor定义在 ImageProcessor.swift。其文档明确对比了两种处理器:
Compared to
ResizingImageProcessor, this processor does not render the images to resize. Instead, it downsamples the input data directly to an image. It is more efficient thanResizingImageProcessor.
Important: Only CG-based images are supported. Animated images (such as GIFs) are not supported.
即:ResizingImageProcessor需要先把完整位图渲染出来再缩放(内存峰值高),而DownsamplingImageProcessor直接从原始数据生成缩略图,全程不展开完整像素。它底层调用 Image.swift 中的KingfisherWrapper.downsampledImage(data:to:scale:),核心是 CoreGraphics 的CGImageSourceCreateThumbnailAtIndex:
let maxDimensionInPixels = max(pointSize.width, pointSize.height) * scale let downsampleOptions: [CFString : Any] = [ kCGImageSourceCreateThumbnailFromImageAlways: true, kCGImageSourceShouldCacheImmediately: true, kCGImageSourceCreateThumbnailWithTransform: true, kCGImageSourceThumbnailMaxPixelSize: maxDimensionInPixels ]- 以"目标尺寸 × scale"计算最大像素边长,交给系统缩略图生成器处理;
kCGImageSourceShouldCache: false避免为取缩略图而全量解码缓存原图;- 输出尺寸与处理器
identifier绑定("com.onevcat.Kingfisher.DownsamplingImageProcessor(\(size))"),不同目标尺寸会生成不同的缓存键——这也是 DiskStorageTests.swift 中"key + 处理器标识"构成缓存文件名的由来。
测试 ImageProcessorTests.swift 验证了降采样输出尺寸符合目标值;KingfisherManagerTests.swift 则验证了DownsamplingImageProcessor与.scaleFactor(2)/.scaleFactor(3)组合下的实际缩放效果。
使用约束提醒:传入的size应小于原始图尺寸;若大于原图,输出将与输入同尺寸、不做降采样(见 ImageProcessor.swift)。同时该处理器不支持 GIF 等动画图(CG 类型之外)。
四、小结与决策建议
| 场景 | 推荐做法 | 对应 API / 选项 |
|---|---|---|
| 列表快速滚动、cell 即将离开屏幕 | 取消不再需要的下载 | imageView.kf.cancelDownloadTask(),配合didEndDisplaying |
| 同一图多版本处理 / 之后需要原图 | 缓存原始图以复用 | .cacheOriginalImage(必要时加.originalCache(_:)) |
| 服务端只有大图、cell 需小图 | 加载前降采样 | .processor(DownsamplingImageProcessor(size:))+.scaleFactor(_:)+.cacheOriginalImage |
将这三项技术组合使用(例如列表页对每张图降采样展示、同时缓存原始图、并在 cell 消失时取消任务),可以在网络、CPU、内存与电池消耗之间取得良好的平衡。更完整的下载、缓存与处理器使用指南,可继续阅读仓库中的 CommonTasks_Downloader.md、CommonTasks_Processor.md 与 CommonTasks_Cache.md。
【免费下载链接】KingfisherA lightweight, pure-Swift library for downloading and caching images from the web.项目地址: https://gitcode.com/GitHub_Trending/ki/Kingfisher
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考