☰
Nuke 14 迁移指南:从 Nuke 13.x 升级的完整 API 变更与适配方案
2026/9/25 14:21:39 网站建设 项目流程
  • 移动开发
  • 图像处理

【免费下载链接】Nuke

Image loading system

项目地址:https://gitcode.com/gh_mirrors/nu/Nuke
点击查看免费下载

本指南面向正在使用 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 工具链:

平台最低版本
iOS16.0
tvOS16.0
macOS13.0
watchOS9.0
visionOS1.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?.image
  • data→container?.data
  • nuke_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 中全部移除,完整对照如下:

移除替代方案
ImagePipelineDelegateImagePipeline.Delegate
ImageRequest.imageIdImageRequest.imageID
ImageRequest.UserInfoKey.imageIdKeyImageRequest.imageID
ImageRequest.UserInfoKey.scaleKeyImageRequest.scale
ImageRequest.UserInfoKey.thumbnailKeyImageRequest.thumbnail
ImagePipeline.Configuration.maximumDecodedImageSizeImageRequest.ThumbnailOptions
ImageDecodingContext.maximumDecodedImageSizeImageRequest.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 给出的类型对齐:

APINuke 13Nuke 14
ImageRequest.scaleFloatCGFloat
ImageRequest.ThumbnailOptions.init(maxPixelSize:)FloatCGFloat

字面量(literal)仍然可以直接赋值,无需改动;如果你之前写了显式转换,现在可以删除:

// Nuke 13 request.scale = Float(traitCollection.displayScale) // Nuke 14 request.scale = traitCollection.displayScale

willCache变为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 13Nuke 14
TaskQueue.maxConcurrentOperationCountTaskQueue.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)直接写入每个方法的签名:

隔离方法
@ImagePipelineActorwillLoadData、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自己发布更新:

APINuke 13Nuke 14
FetchImage.progress、LazyImageState.progressFetchImage.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。分支判断不再需要类型转换:

APINuke 13Nuke 14
FetchImage.result、LazyImageState.resultResult<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.onFailureany ErrorImagePipeline.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 后,建议按以下顺序排查存量代码:

  1. 模块导入:把import NukeExtensions全部改为import NukeUI,并按模块名调用的函数同步替换前缀;
  2. 自定义显示视图:删除UIImageView子类中的nuke_display(image:data:)override,改为在自定义UIView上直接声明ImageDisplaying并实现nuke_display(_ container: ImageContainer?);
  3. 动图处理:优先读取container.animation判断可播放性;不播放动图的 App 可关闭isAnimatedImageParsingEnabled;
  4. 异步化:将 Combine publisher 代码改写为image(for:)/imageTask(with:)+ImageTask.previews;把带完成闭包的 delegate 方法改为 async/await 形式;
  5. 类型与命名:把Float相关转换删除、改用CGFloat;TaskQueue使用maxConcurrentTaskCount;ImageRequest初始化器去掉userInfo参数;
  6. 错误与进度:删除对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

项目地址:https://gitcode.com/gh_mirrors/nu/Nuke
点击查看免费下载
上一篇:魔兽争霸3终极兼容性解决方案:5分钟上手WarcraftHelper插件
下一篇:为什么Terraformer值得关注?告别3大痛点,搞定代码漂移与无主遗留基础设施

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询