- 移动开发
- 图像处理
【免费下载链接】Nuke
Image loading system
本指南面向正在使用 Nuke 13.x 并准备升级到 Nuke 14 的开发者,系统梳理了 Nuke 14 在模块结构、动画解析、API 命名、并发模型与 SwiftUI 集成方面的全部破坏性变更,并给出可复制的迁移代码。阅读完成后,你将能够把现有代码库平滑迁移到 Nuke 14,理解NukeUI与ImageDisplaying的新职责边界,掌握 Async/Await 时代下管线、任务与委托的用法,并提前识别 Nuke 15 中即将移除的兼容层。
说明:Nuke 14 处于开发中(Work in Progress),本指南会随变更持续更新;文中涉及的默认值与行为均以当前仓库源码为准。
最低系统要求:一次彻底的基础线提升
Nuke 14 全面抬升了最低支持平台,同时要求最新的 Apple 工具链:
| 平台 | 最低版本 |
|---|---|
| iOS | 16.0 |
| tvOS | 16.0 |
| macOS | 13.0 |
| watchOS | 9.0 |
| visionOS | 1.0 |
- 编译器与语言要求:Xcode 26.0、Swift 6.2。
- 仍需要支持更早系统版本的 App 可以继续停留在Nuke 13.x,该分支会持续接收修复。
这一基线提升是 Nuke 14 一系列结构性改动(严格并发隔离、全 Swift Task 化的任务队列、移除旧 API)的前提,迁移前请先确认构建环境满足上表要求。
NukeExtensions并入NukeUI:三模块格局确立
Nuke 14 将原本位于NukeExtensions的图片视图扩展整体迁入NukeUI——NukeUI本来就是 UIKit/AppKit 视图(LazyImageView、AnimatedImageView等)的所在地。调整后,包只发布三个模块:
Nuke:管线、解码、处理、缓存等核心能力;NukeUI:所有视图扩展与 SwiftUI 组件;NukeVideo:视频解码与播放(AVDataAsset、VideoPlayerView)。
NukeExtensions目前仍以空模块的形式存在,仅负责 re-exportNukeUI,保证存量代码继续编译;从源码注释看,它计划在 Nuke 15 移除(见 Sources/NukeExtensions/Exports.swift)。因此新代码应直接改用NukeUI:
// Before import NukeExtensions // After import NukeUI如果此前按模块名引用了这些函数,请同步更新前缀:
// Before NukeExtensions.loadImage(with: url, into: imageView) // After NukeUI.loadImage(with: url, into: imageView)Nuke_ImageDisplaying更名为ImageDisplaying:拥抱整个ImageContainer
协议签名变化
Nuke 13 的Nuke_ImageDisplaying是@objc协议,方法接收拆开的image与data两个参数;Nuke 14 中去掉了@objc与前缀,方法改为接收整个ImageContainer——管线解析出的动图数据就在这个容器里:
// Nuke 13 extension MyImageView: Nuke_ImageDisplaying { func nuke_display(image: UIImage?, data: Data?) { self.image = image } } // Nuke 14 extension MyImageView: ImageDisplaying { func nuke_display(_ container: ImageContainer?) { self.image = container?.image } }迁移对照关系:
image→container?.imagedata→container?.datanuke_display(image: nil, data: nil)(清空视图)→nuke_display(nil)- 需要播放动图时,直接使用已解析好的
container?.animation
方法保留nuke_前缀,是因为内置一致性(conformance)以系统类扩展(extension)的形式随包提供,不带前缀的命名可能与未来的系统 API 冲突(见 Sources/NukeUI/ImageViewExtensions.swift 的协议注释)。ImageDisplayingView(iOS/tvOS/visionOS 上为UIView & ImageDisplaying,macOS 上为NSObject & ImageDisplaying)保持不变。
破坏性影响:UIImageView 子类中重写显示方法不再生效
Swift 对在扩展中声明的协议见证(witness)采取静态决议,因此在UIImageView子类里 override 显示方法将永远不会被调用:
// Nuke 13 —— Nuke 14 中不再生效 final class MyImageView: UIImageView { override func nuke_display(image: UIImage?, data: Data?) { ... } }正确做法是让自定义视图直接声明一致性,自己持有内部UIImageView:
// Nuke 14 final class MyImageView: UIView, ImageDisplaying { private let imageView = UIImageView() func nuke_display(_ container: ImageContainer?) { imageView.image = container?.image } }协议注释中给出了更贴近真实场景的写法:渲染器可以从容器中同时拿到静态图与已解析的animation(见 Sources/NukeUI/ImageViewExtensions.swift):
final class MyImageView: UIView, ImageDisplaying { func nuke_display(_ container: ImageContainer?) { guard let animation = container?.animation else { return show(still: container?.image) } myEngine.play(animation) } }如果子类的目的只是为了播放动图,直接使用AnimatedImageView即可——它本身就继承了扩展中的ImageDisplaying一致性,并在内部通过displayContainer(_:)将整个容器交给display(container)处理(见 Sources/NukeUI/ImageViewExtensions.swift)。
管线开始解析动图:ImageContainer.animation登场
解析职责上移
在 Nuke 13 中,ImageDecoders.Default只是把动图的编码字节附加到ImageContainer.data;Nuke 14 进一步解析这些数据并把结果放入新增的ImageContainer.animation(类型为AnimatedImageSource)。以展示动图为例:
// Nuke 13 guard let source = AnimatedImageSource(data: response.container.data ?? Data()) else { return } // Nuke 14 guard let animation = response.container.animation else { return }AnimatedImageSource从NukeUI移到了Nuke核心模块;NukeUI会 re-exportNuke,所以导入任一模块的既有代码都能继续编译。
解析的时机、成本与关闭开关
从默认解码器实现可以确认解析的具体行为(见 Sources/Nuke/Decoding/ImageDecoders+Default.swift):
- 解码时先通过
AssetType.isAnimated(data:type:)做头字节嗅探(header sniff),命中后再决定是否附加data; - 解析(
AnimatedImageSource(data:))在解码队列上执行,每张被解码的图像只解析一次,结果随容器一起缓存; - 解析会逐帧遍历元数据(如每帧的 delay),无论图像最终是否被显示,成本都会发生。文档与源码注释都强调:
container.data != nil只是头嗅探,对单帧 GIF 也为true;而container.animation != nil才是"能否播放"的已解析答案,判断可播放性时应优先使用它。
如果 App 根本不需要播放动图(或用自己的渲染引擎播放),可以关闭解析:
ImagePipeline.shared = ImagePipeline { $0.isAnimatedImageParsingEnabled = false }该开关在ImagePipeline.Configuration中的默认值为true(见 Sources/Nuke/Pipeline/ImagePipeline+Configuration.swift),并通过ImageDecodingContext.isAnimatedImageParsingEnabled一路传到解码器(见 Sources/Nuke/Decoding/ImageDecoderRegistry.swift)。
两条需要注意的关联行为:
ImageContainer.data不受该开关影响,因此自己解析数据的渲染器不受干扰;- 处理(processing)图像会同时清空
data与animation——它们描述的是进入处理器之前的图像。ImageContainer.map(_:)的源码明确执行了copy.data = nil; copy.animation = nil(见 Sources/Nuke/ImageContainer.swift)。另外,animation与data共享同一缓冲区(AnimatedImageSource.data即data),所以附加动画只额外增加帧延迟信息的内存成本(见 Sources/Nuke/ImageContainer.swift)。
移除 Nuke 13 中已废弃的 API
Nuke 13 标记废弃的 API 在 Nuke 14 中全部移除,完整对照如下:
| 移除 | 替代方案 |
|---|---|
ImagePipelineDelegate | ImagePipeline.Delegate |
ImageRequest.imageId | ImageRequest.imageID |
ImageRequest.UserInfoKey.imageIdKey | ImageRequest.imageID |
ImageRequest.UserInfoKey.scaleKey | ImageRequest.scale |
ImageRequest.UserInfoKey.thumbnailKey | ImageRequest.thumbnail |
ImagePipeline.Configuration.maximumDecodedImageSize | ImageRequest.ThumbnailOptions |
ImageDecodingContext.maximumDecodedImageSize | ImageRequest.ThumbnailOptions |
关于maximumDecodedImageSize:其背后的自动降采样(automatic downscaling)实现早在 Nuke 13 就被移除,所以在 Nuke 13 中设置它已经没有任何效果。Nuke 14 中请改用ImageRequest.ThumbnailOptions按请求(per-request)控制解码图像尺寸。
ImageRequest初始化器移除userInfo参数
userInfo参数在 Nuke 13 中已被软废弃,Nuke 14 中从ImageRequest的初始化器中移除。原先经它传递的选项现在都有专用的类型安全属性(scale、thumbnail等,见 Sources/Nuke/ImageRequest.swift),userInfo本身仍作为属性保留,供自定义值使用:
// Before let request = ImageRequest(url: url, userInfo: ["key": "value"]) // After var request = ImageRequest(url: url) request.userInfo = ["key": "value"]移除 Combine 支持:全面转向 Async/Await
Nuke 14 移除了全部 Combine API,改用 Async/Await:
| 移除 | 替代方案 |
|---|---|
ImagePipeline.imagePublisher(with:) | ImagePipeline.image(for:)或ImagePipeline.imageTask(with:) |
FetchImage.load(_:)(接收Publisher) | FetchImage.load(_:)(接收 async 闭包) |
典型迁移:
// Nuke 13 cancellable = ImagePipeline.shared.imagePublisher(with: url) .sink(receiveCompletion: { _ in }, receiveValue: { response in imageView.image = response.image }) // Nuke 14 imageView.image = try await ImagePipeline.shared.image(for: url)- 原来由 publisher 作为中间值(intermediate values)发射的渐进解码预览,改用
ImageTask.previews观察; FetchImage仍然是ObservableObject,从 SwiftUI 中观察它的用法不变。
移除ImageTask.Event.started
ImageTask.Event.started从未投递给ImageTask.events——管线是直接向 delegate 报告的,因此它实际上只为ImagePipeline.Delegate存在。Nuke 14 为 delegate 提供了专门方法:
// Nuke 13 func imageTask(_ task: ImageTask, didReceiveEvent event: ImageTask.Event, pipeline: ImagePipeline) { switch event { case .started: handleStart(task) case .progress, .preview, .finished: break } } // Nuke 14 func imageTaskDidStart(_ task: ImageTask, pipeline: ImagePipeline) { handleStart(task) }缩放与尺寸 API:Float全面改为CGFloat
公共的 scale 与 size API 统一改用CGFloat,与 UIKit / SwiftUI 给出的类型对齐:
| API | Nuke 13 | Nuke 14 |
|---|---|---|
ImageRequest.scale | Float | CGFloat |
ImageRequest.ThumbnailOptions.init(maxPixelSize:) | Float | CGFloat |
字面量(literal)仍然可以直接赋值,无需改动;如果你之前写了显式转换,现在可以删除:
// Nuke 13 request.scale = Float(traitCollection.displayScale) // Nuke 14 request.scale = traitCollection.displayScalewillCache变为async
ImagePipeline.Delegate.willCache(data:image:for:pipeline:)不再接收完成闭包,而是直接返回要存储的数据;返回nil表示禁止缓存:
// Nuke 13 func willCache(data: Data, image: ImageContainer?, for request: ImageRequest, pipeline: ImagePipeline, completion: @escaping (Data?) -> Void) { completion(shouldStore(request) ? data : nil) } // Nuke 14 func willCache(data: Data, image: ImageContainer?, for request: ImageRequest, pipeline: ImagePipeline) async -> Data? { shouldStore(request) ? data : nil }该方法运行在@ImagePipelineActor上(见 Sources/Nuke/Pipeline/ImagePipeline+Delegate.swift),管线在存储数据前会等待它完成。
TaskQueue.maxConcurrentOperationCount更名
TaskQueue背后已经没有 Operation(OperationQueue时代的产物)——它的每一个工作单元都是 SwiftTask,因此旧名字不再贴切。旧名字仍然可用,但已标记废弃(见 Sources/Nuke/Pipeline/TaskQueue.swift):
| Nuke 13 | Nuke 14 |
|---|---|
TaskQueue.maxConcurrentOperationCount | TaskQueue.maxConcurrentTaskCount |
TaskQueue(maxConcurrentOperationCount:) | TaskQueue(maxConcurrentTaskCount:) |
// Nuke 13 let pipeline = ImagePipeline { $0.imageProcessingQueue.maxConcurrentOperationCount = 4 } // Nuke 14 let pipeline = ImagePipeline { $0.imageProcessingQueue.maxConcurrentTaskCount = 4 }从源码看,maxConcurrentTaskCount的默认值是ProcessInfo.processInfo.processorCount(处理器核心数,见 Sources/Nuke/Pipeline/TaskQueue.swift),且队列内部用OSAllocatedUnfairLock保护的Limits状态管理并发与预留任务数(见 Sources/Nuke/Pipeline/TaskQueue.swift)。
ImagePipeline.Delegate声明隔离(isolation)
Nuke 13 的 delegate 协议只在文档注释中描述方法运行位置("performed on the pipeline queue in the background"),且该描述只对一半方法成立。Nuke 14 将隔离(isolation)直接写入每个方法的签名:
| 隔离 | 方法 |
|---|---|
@ImagePipelineActor | willLoadData、willCache、imageTaskDidStart、imageTask(_:didReceiveEvent:) |
nonisolated | 其余所有:工厂方法、cacheKey、策略方法、decompress、imageTaskCreated |
(协议签名可参见 Sources/Nuke/Pipeline/ImagePipeline+Delegate.swift。)
迁移影响很小:普通(无隔离声明)方法依然能满足隔离要求,因此大多数 conformer 无需改动;只有带冲突隔离的方法才会被拒绝。此前无法通过一致性检查的@MainActordelegate,现在反而可以正常工作。
FetchImage.Progress替换为ImageTask.Progress
NukeUI曾有自己的进度类型——一个嵌套在另一个ObservableObject里的ObservableObject,必须用独立的@ObservedObject去观察。现在FetchImage.progress与LazyImageState.progress都返回ImageTask.Progress——与管线报告的是同一个值类型,且由FetchImage自己发布更新:
| API | Nuke 13 | Nuke 14 |
|---|---|---|
FetchImage.progress、LazyImageState.progress | FetchImage.Progress(class) | ImageTask.Progress(struct) |
// Nuke 13 LazyImage(url: url) { state in if state.isLoading { DownloadProgressView(progress: state.progress) } } struct DownloadProgressView: View { @ObservedObject var progress: FetchImage.Progress var body: some View { ProgressView(value: progress.fraction) } } // Nuke 14 LazyImage(url: url) { state in if state.isLoading { ProgressView(value: state.progress.fraction) } }相关实现细节:
FetchImage.Progress现在是标记废弃的 typealias,指向ImageTask.Progress(见 Sources/NukeUI/Deprecated.swift),并将在 Nuke 15 移除;ImageTask.Progress是Hashable, Sendable的结构体(见 Sources/Nuke/ImageTask.swift);- 进度更新仅在读取
progress时才发布:读取该属性会让对象选择发布进度,因此不展示进度的视图不会因为每个数据块到达而被无效化重绘(见 Sources/NukeUI/FetchImage.swift)。
NukeUI直接暴露ImagePipeline.Error
管线总是以ImagePipeline.Error结束每个任务,因此NukeUI不再把它擦除成any Error。分支判断不再需要类型转换:
| API | Nuke 13 | Nuke 14 |
|---|---|---|
FetchImage.result、LazyImageState.result | Result<ImageResponse, any Error>? | Result<ImageResponse, ImagePipeline.Error>? |
FetchImage.onCompletion、LazyImage.onCompletion(_:)、LazyImageView.onCompletion | (Result<ImageResponse, any Error>) -> Void | (Result<ImageResponse, ImagePipeline.Error>) -> Void |
LazyImageState.error、LazyImageView.onFailure | any Error | ImagePipeline.Error |
// Nuke 13 LazyImage(url: url) { state in if let error = state.error as? ImagePipeline.Error, case .dataDownloadExceededMaximumSize = error { Text("Image too large") } } // Nuke 14 LazyImage(url: url) { state in if case .dataDownloadExceededMaximumSize = state.error { Text("Image too large") } }LazyImage.onCompletion(_:)等闭包的签名变化与LazyImageState.error的类型收窄均可从源码确认(见 Sources/NukeUI/LazyImage.swift)。
一个例外:FetchImage.load(_:)仍接受无类型的 async 闭包。如果闭包抛出的错误不是ImagePipeline.Error,会被包装成dataLoadingFailed(error:)报告——这与管线对 asyncImageRequest源抛错的报告方式一致。
迁移自检清单
升级到 Nuke 14 后,建议按以下顺序排查存量代码:
- 模块导入:把
import NukeExtensions全部改为import NukeUI,并按模块名调用的函数同步替换前缀; - 自定义显示视图:删除
UIImageView子类中的nuke_display(image:data:)override,改为在自定义UIView上直接声明ImageDisplaying并实现nuke_display(_ container: ImageContainer?); - 动图处理:优先读取
container.animation判断可播放性;不播放动图的 App 可关闭isAnimatedImageParsingEnabled; - 异步化:将 Combine publisher 代码改写为
image(for:)/imageTask(with:)+ImageTask.previews;把带完成闭包的 delegate 方法改为 async/await 形式; - 类型与命名:把
Float相关转换删除、改用CGFloat;TaskQueue使用maxConcurrentTaskCount;ImageRequest初始化器去掉userInfo参数; - 错误与进度:删除对
any Error的强转,直接匹配ImagePipeline.Error的 case;用值类型的ImageTask.Progress替换@ObservedObject var progress: FetchImage.Progress。
延伸阅读
- 迁移到 Nuke 14 之前的版本升级路线:Nuke 13 Migration Guide、Nuke 12 Migration Guide
- 新增的动画解析与播放能力:AnimatedImages.md
- SwiftUI 集成:swiftui.md;UIKit 集成:uikit.md
- 核心实现源码:ImagePipeline.swift、ImageContainer.swift、ImageViewExtensions.swift、TaskQueue.swift
- 移动开发
- 图像处理
【免费下载链接】Nuke
Image loading system
相关推荐
Nuke 10 迁移指南:从 Nuke 9.x 平滑升级 Image Loading System 的完整实战手册
Nuke 10 迁移指南:从 Nuke 9.x 平滑升级 Image Loading System 的完整实战手册 本文以 Nuke 官方 Nuke 10 Mi
移动开发图像处理Nuke 7 迁移指南:从 Nuke 6.x 平滑升级到 ImagePipeline 时代的完整实操手册
Nuke 7 迁移指南:从 Nuke 6.x 平滑升级到 ImagePipeline 时代的完整实操手册 Nuke 7 是 Nuke 图片加载框架(本仓库 So
移动开发图像处理Nuke 4 迁移指南:从 Nuke 3.x 升级到 Swift 3 时代的架构重构实践
Nuke 4 迁移指南:从 Nuke 3.x 升级到 Swift 3 时代的架构重构实践 本文是 Nuke 图片加载框架 4.x 版本的官方迁移指南,系统梳理了
移动开发图像处理
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考