☰
Nuke 13 迁移指南:从 Nuke 12 升级的类型化错误、类型安全属性与并发模型全面解析
2026/9/25 3:36:32 网站建设 项目流程
  • 移动开发
  • 图像处理

【免费下载链接】Nuke

Image loading system

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

本文基于 Nuke 官方迁移文档 Nuke 13 Migration Guide,系统梳理从 Nuke 12.x 升级到 13 的全部破坏性变更:包括Typed Throws错误模型、ImageRequest类型安全属性、ImagePipeline.Delegate嵌套化、任务事件模型重构、TaskQueue并发队列以及@MainActor @Sendable回调约束。读完本文,你将掌握每项变更的迁移写法、背后的源码设计动机,以及一份可直接照做的升级清单。

迁移概览:最小系统要求与升级前提

Nuke 13 是一次以 Swift 并发(Swift Concurrency)安全为核心的大版本升级,整体抬升了平台与工具链下限:

  • 最低系统版本:iOS 15.0、tvOS 15.0、macOS 12.0、watchOS 8.0、visionOS 1.0
  • 最低 Xcode 版本:Xcode 26.0
  • 最低 Swift 版本:Swift 6.2

这意味着所有使用旧版本系统部署的应用都需要同步抬升 deployment target。升级前请先确认工程满足上述工具链与系统版本要求。

从源码结构看,Nuke 13 的破坏性变更集中体现在四个层面:错误模型(Typed Throws)、请求 API(ImageRequest属性)、扩展点(Delegate 嵌套化)与并发模型(TaskQueue+ 主线程隔离回调)。下面逐项展开。

类型化错误(Typed Throws):错误模型的一次统一

变更核心

Nuke 13 中,以下四个 API 从普通throws改为类型化抛出(throws(ImagePipeline.Error)):

  • ImageTask.image
  • ImageTask.response
  • ImagePipeline.image(for:)
  • ImagePipeline.data(for:)

类型化抛出意味着编译器现在确切知道这些方法只会抛出ImagePipeline.Error,catch 分支不再需要区分"管道错误"和"其他错误"。同时新增了两个错误 case:

  • ImagePipeline.Error.cancelled—— 任务被取消时抛出(旧版本抛的是CancellationError)
  • ImagePipeline.Error.dataDownloadExceededMaximumSize—— 下载数据量超过Configuration.maximumResponseDataSize时抛出

迁移写法对照

// Before (Nuke 12) do { let image = try await ImagePipeline.shared.image(for: url) } catch let error as ImagePipeline.Error { // handle pipeline error } catch { // handle other errors, including CancellationError } // After (Nuke 13) do { let image = try await ImagePipeline.shared.image(for: url) } catch { // error is always ImagePipeline.Error switch error { case .cancelled: break case .dataLoadingFailed: break // ... } }

源码层面的完整错误模型

ImagePipeline+Error.swift 定义了完整的错误枚举,共 11 个 case。其中携带解码或处理上下文的 case 声明为indirect——源码注释解释了这个设计:如果把这些上下文内联存储,每个错误(以及持有错误的每个Result、每个任务事件)都会膨胀约一百字节:

  • dataMissingInCache:数据未缓存且指定了returnCacheDataDontLoad选项
  • dataLoadingFailed(error:):数据加载器失败(包裹底层错误)
  • dataIsEmpty:数据加载器返回空数据
  • decoderNotRegistered(context:):没有注册对应解码器(仅当配置了自定义解码器时可能抛出)
  • decodingFailed(decoder:context:error:):解码器未能产出最终图像
  • processingFailed(processor:context:error:):处理器未能产出最终图像
  • imageRequestMissing:调用加载方法时没有请求或 URL
  • pipelineInvalidated:管道已失效,无法再发起请求
  • dataDownloadExceededMaximumSize:下载数据超过maximumResponseDataSize限制
  • cancelled:图像任务被取消

该文件还提供了两个实用的辅助 API(ImagePipeline+Error.swift):

  • error.isCancelled:判断是否为取消。源码注释特别强调:"取消是预期结果而非失败——每一个滚出屏幕的 image view 都会产生一次取消",因此建议用它把取消从错误 UI 和日志中过滤掉:
guard !error.isCancelled else { return } logger.error("Failed to load image: \(error)")
  • error.dataLoadingError:取出被包裹的底层数据加载错误。

ImageRequest:从 userInfo 字典到类型安全属性

变更核心

Nuke 12 中三个通过userInfo字典传递的请求参数,在 Nuke 13 中被替换为ImageRequest上的专用类型安全属性,旧键被标记为废弃:

// Before (Nuke 12) var request = ImageRequest(url: url) request.userInfo[.imageIdKey] = "http://example.com/image.jpeg" request.userInfo[.scaleKey] = 2.0 as Float request.userInfo[.thumbnailKey] = ImageRequest.ThumbnailOptions(maxPixelSize: 400) // After (Nuke 13) var request = ImageRequest(url: url) request.imageID = "http://example.com/image.jpeg" request.scale = 2.0 request.thumbnail = ImageRequest.ThumbnailOptions(maxPixelSize: 400)

imageId → imageID:改名并开放写入

原有的只读属性imageId被重命名为imageID(大写 "ID"),并改为可读写:

// Before (Nuke 12) let id: String? = request.imageId // read-only // After (Nuke 13) var id: String? = request.imageID // read/write

从源码看,imageID的 getter 是"自定义 ID 优先、否则回退到原始 ID"(ImageRequest.swift):customImageID ?? originalImageID,其中原始 ID 在init(url:)/init(urlRequest:)/init(id:data:)等初始化器中自动生成。官方注释给出了一个典型场景——剥离 URL 中的瞬态查询参数以保证缓存键稳定:

var request = ImageRequest(url: URL(string: "http://example.com/image.jpeg?token=123")) request.imageID = "http://example.com/image.jpeg"

scale属性默认值为1(ImageRequest.swift),thumbnail为ThumbnailOptions?,设置后管道将生成缩略图而非完整图像——官方注释指出缩略图创建在内存占用上通常显著优于ImageProcessors.Resize(要求使用默认解码器)。

userInfo 类型的 Sendable 化

userInfo字典类型从[UserInfoKey: Any]变更为[UserInfoKey: any Sendable](ImageRequest.swift),这是为 Swift 6 严格并发检查服务的。UserInfoKey仍保留了一个官方内置键labelKey,用于在ImageTask.Metrics.label中记录请求角色(如"feed"、"avatar"),除此之外管道不做任何处理(ImageRequest.swift)。

这些属性如何进入缓存键

从源码结构看,imageID、scale、thumbnail三者的影响远不止请求本身——它们直接参与内存缓存键与任务合并键的哈希计算:

  • MemoryCacheKey组合了imageID、scale、thumbnail与处理器标识(ImageRequestKeys.swift);
  • TaskFetchOriginalImageKey同样组合scale与thumbnail(ImageRequestKeys.swift)。

因此,为同一 URL 设置不同scale或thumbnail会产生不同的缓存条目与任务,这正是类型安全属性在底层被消费的方式。

旧 API 的处置方式

旧符号在 Deprecated.swift 中以@available(*, unavailable, renamed:/message:)形式保留为不可用桩,编译器错误会直接给出替换建议,renamed:还会生成 Xcode fix-it(源码注释说明这些桩会在移除两个大版本后删除):

  • ImagePipelineDelegate→renamed: "ImagePipeline.Delegate"
  • imageId→renamed: "imageID"
  • imageIdKey→message: "Use ImageRequest.imageID"
  • scaleKey→message: "Use ImageRequest.scale"
  • thumbnailKey→message: "Use ImageRequest.thumbnail"

ImagePipeline.Delegate:从顶层协议到嵌套类型

变更核心

ImagePipelineDelegate被重命名为ImagePipeline.Delegate,并定义为ImagePipeline的嵌套类型。官方保留了废弃的 typealias 以兼容旧代码,但建议尽快更新:

// Before (Nuke 12) final class MyDelegate: ImagePipelineDelegate { ... } // After (Nuke 13) final class MyDelegate: ImagePipeline.Delegate { ... }

源码中的协议全貌

从 ImagePipeline+Delegate.swift 可以看到,Delegate协议本身也全面 Sendable 化(protocol Delegate: AnyObject, Sendable),并且每个方法都在签名中声明了隔离性:

  • nonisolated定制类方法(工厂、缓存键、策略、解压):管道可以从任何需要的上下文(包括同步的ImagePipeline.CacheAPI)调用它们,如imageDecoder(for:pipeline:)、imageEncoder(for:pipeline:)、previewPolicy(for:pipeline:)、dataLoader(for:pipeline:)、imageCache(for:pipeline:)、dataCache(for:pipeline:)、cacheKey(for:pipeline:)、shouldDecompress(response:for:pipeline:)、decompress(response:request:pipeline:);
  • @ImagePipelineActor通知类方法:运行在管道 actor 上,delegate 可以在无锁状态下维护自身状态,如willLoadData(for:urlRequest:pipeline:)(可用于注入鉴权 token、签名请求,抛错即取消请求)、willCache(data:image:for:pipeline:)(返回nil或空数据可阻止缓存)、imageTaskDidStart(_:pipeline:)、imageTask(_:didReceiveEvent:pipeline:);
  • 特例imageTaskCreated(_:pipeline:):在创建任务的上下文中立即调用,同样标记为nonisolated。

协议扩展为每个方法提供了默认实现(ImagePipeline+Delegate.swift),多数直接转发到pipeline.configuration的对应配置。管道在无自定义 delegate 时会跳过钩子调用(内部通过isDefaultDelegate判断,见 ImagePipeline+Delegate.swift),因此默认场景零开销。

逐事件 delegate 方法的移除

几个软废弃(soft-deprecated)的逐事件 delegate 方法在 Nuke 13 中被彻底移除,统一收敛到imageTask(_:didReceiveEvent:pipeline:):

// Before (Nuke 12) — these methods no longer exist func imageTaskDidStart(_ task: ImageTask, pipeline: ImagePipeline) { ... } func imageTask(_ task: ImageTask, didUpdateProgress progress: ImageTask.Progress, pipeline: ImagePipeline) { ... } func imageTask(_ task: ImageTask, didReceivePreview response: ImageResponse, pipeline: ImagePipeline) { ... } func imageTaskDidCancel(_ task: ImageTask, pipeline: ImagePipeline) { ... } func imageTask(_ task: ImageTask, didCompleteWithResult result: Result<ImageResponse, ImagePipeline.Error>, pipeline: ImagePipeline) { ... } // After (Nuke 13) func imageTask(_ task: ImageTask, didReceiveEvent event: ImageTask.Event, pipeline: ImagePipeline) { switch event { case .started: break case .progress(let progress): break case .preview(let response): break case .finished(let result): break } }

ImageTask.Event 变化:取消统一为失败结果

变更核心

ImageTask.Event.cancelledcase 被移除——取消不再是一种独立事件,而是统一表示为失败结果;同时新增.startedcase:

// Before (Nuke 12) public enum Event: Sendable { case progress(Progress) case preview(ImageResponse) case cancelled case finished(Result<ImageResponse, ImagePipeline.Error>) } // After (Nuke 13) @frozen public enum Event: Sendable { case started case progress(Progress) case preview(ImageResponse) case finished(Result<ImageResponse, ImagePipeline.Error>) // .cancelled → .finished(.failure(.cancelled)) }

如果你之前显式处理过.cancelled,请改为检查.finished(.failure(.cancelled)):

// Before (Nuke 12) case .cancelled: handleCancellation() // After (Nuke 13) case .finished(.failure(.cancelled)): handleCancellation()

源码中的事件模型

ImageTask.swift 中Event枚举为@frozen public enum Event: Sendable,官方注释明确了取消语义:任务被取消时,finished会以.failure(.cancelled)回调。任务通过events: AsyncStream<Event>对外发布事件流(ImageTask.swift),它会在已有任务上重放终态事件、在下载中途订阅时先发出当前进度事件,并为慢消费者缓冲未消费事件。任务的状态快照Status也包含result: Result<ImageResponse, ImagePipeline.Error>?与isCancelled: Bool字段(ImageTask.swift),取消标记在发送finished事件之前被记录,因此事件观察者与等待response的调用方都能读到一致的结果。

Configuration:队列类型变更与新属性

队列从 OperationQueue 变为 TaskQueue

管道的五个工作队列从OperationQueue改为全新的TaskQueue类型——一个在ImagePipelineActor上同步的、优先级感知的并发限制队列,每个并发操作由一个 SwiftTask支撑(TaskQueue.swift)。待处理操作按优先级分桶存储(FIFO 同优先级先出),保证最高优先级的工作总是先被取出;reservedTaskCount则保证低优先级工作(如预取)不会占满所有槽位,高优先级请求总能获得空闲槽(TaskQueue.swift)。

TaskQueue保留了部分既有 API 签名(maxConcurrentTaskCount、isSuspended等),并支持运行中动态调整。各队列在 ImagePipeline+Configuration.swift 中的默认并发数如下:

队列默认最大并发任务数说明
dataLoadingQueue6(其中保留 3 个槽位给高优先级)优先级低于.normal的请求(如预取)最多占用 3 个槽
imageDecodingQueue2仅异步解码器在此运行
imageEncodingQueue1—
imageProcessingQueue2—
imageDecompressingQueue2—

Configuration仍是 struct,但官方注释特别警告了一个共享语义陷阱:TaskQueue是 class,复制配置会共享队列而非复制。修改副本的队列会同时影响原管道(ImagePipeline+Configuration.swift):

var configuration = ImagePipeline.shared.configuration configuration.imageProcessingQueue.maxConcurrentOperationCount = 1 // ImagePipeline.shared is now throttled as well

若要为新管道独立调队列而不影响已有管道,应从全新配置(Configuration()或预定义配置)开始,它们各自创建自己的队列。

另外,被废弃的callbackQueue与dataCachingQueue属性已彻底移除。

新增配置属性一:progressiveDecodingInterval

progressiveDecodingInterval控制两次渐进解码尝试之间的最小间隔(秒)。当数据到达速度快于该间隔时,中间数据块会被跳过。默认值为0.5秒(ImagePipeline+Configuration.swift)。它配合isProgressiveDecodingEnabled(默认false)使用,后者开启后管道会在每次收到新数据块时尝试产出预览图,是否产出取决于解码器(默认解码器支持渐进式 JPEG)。

新增配置属性二:maximumResponseDataSize

maximumResponseDataSize限制允许的最大响应数据字节数,超过该限制的下载会被自动取消,并以ImagePipeline.Error.dataDownloadExceededMaximumSize失败。默认值是物理内存的 10%,上限 200 MB(ImagePipeline+Configuration.swift):

public var maximumResponseDataSize: Int? = { let physicalMemory = ProcessInfo.processInfo.physicalMemory let limit = min(209_715_200 /* 200 MB */, physicalMemory / 10) return Int(limit) }()

如果你之前在 Nuke 12 中未设置任何限制、希望保持原行为,请显式设为nil关闭该检查:

configuration.maximumResponseDataSize = nil

回调闭包:@MainActor @Sendable 全面隔离

Nuke 13 中,公开 API 的所有回调闭包都被标注为@MainActor @Sendable。如果你的闭包不是主 actor 隔离的,这将是源码破坏性变更。好处是:闭包隐式运行在主 actor 上,访问self不再需要手动派发到主线程。

ImagePipeline 基于闭包的 API:

// Before (Nuke 12) pipeline.loadImage(with: request) { result in self.imageView.image = try? result.get().image } // After (Nuke 13) — closure is @MainActor, self access is safe pipeline.loadImage(with: request) { result in self.imageView.image = try? result.get().image }

语法保持不变,但闭包现在隐式@MainActor。官方迁移文档明确指出:如果self不是@MainActor,在闭包中不加[weak self]捕获self会产生编译警告,请相应更新闭包写法。

NukeUI 回调(FetchImage、LazyImage、LazyImageView)同样适用:

// Before (Nuke 12) lazyImageView.onSuccess = { response in print(response.image) } // After (Nuke 13) — same syntax, but now @MainActor @Sendable lazyImageView.onSuccess = { response in print(response.image) }

升级清单:从 Nuke 12 到 Nuke 13 的速查表

  1. 抬升系统与工具链版本:iOS 15.0 / tvOS 15.0 / macOS 12.0 / watchOS 8.0 / visionOS 1.0,Xcode 26.0,Swift 6.2。
  2. 收敛错误处理:image(for:)、data(for:)、ImageTask.image/response现在只抛ImagePipeline.Error,删除旧的catch let error as ImagePipeline.Error+ 兜底catch双重分支;新增的.cancelled建议用error.isCancelled过滤。
  3. 替换 userInfo 键:imageIdKey→request.imageID(注意大小写,且现在是可读写属性),scaleKey→request.scale,thumbnailKey→request.thumbnail。
  4. 重命名 Delegate:ImagePipelineDelegate→ImagePipeline.Delegate;移除旧的逐事件 delegate 方法,统一到imageTask(_:didReceiveEvent:pipeline:)。
  5. 改写取消事件:Event.cancelled→Event.finished(.failure(.cancelled));如需任务开始事件,使用新增的Event.started。
  6. 检查队列配置:callbackQueue、dataCachingQueue已移除;OperationQueue相关配置迁移到TaskQueue的同名 API,注意配置复制共享队列的语义。
  7. 审视新增默认限制:确认maximumResponseDataSize(默认内存 10%、上限 200 MB)是否符合业务,不需要限制则设为nil;progressiveDecodingInterval默认 0.5 秒,可按需调整。
  8. 适配主线程闭包:确认传入的闭包符合@MainActor @Sendable约束,必要时为self捕获补充[weak self]或让类型主 actor 隔离。

完成上述八项后,你的工程即可平稳运行在 Nuke 13 上。若在迁移中遇到具体符号无法解析,可对照 Deprecated.swift 中的 unavailable 桩——编译器错误信息里通常已写明推荐的替换 API 或 fix-it。

  • 移动开发
  • 图像处理

【免费下载链接】Nuke

Image loading system

项目地址:https://gitcode.com/gh_mirrors/nu/Nuke
点击查看免费下载
上一篇:从创意到视频只需3分钟:Pixelle-Video开源AI视频引擎完全指南
下一篇:终极Potree虚拟现实体验:如何在VR环境中沉浸式探索点云世界

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

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

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

立即咨询