- 移动开发
- 图像处理
【免费下载链接】Nuke
Image loading system
本文基于 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.imageImageTask.responseImagePipeline.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:调用加载方法时没有请求或 URLpipelineInvalidated:管道已失效,无法再发起请求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 中的默认并发数如下:
| 队列 | 默认最大并发任务数 | 说明 |
|---|---|---|
dataLoadingQueue | 6(其中保留 3 个槽位给高优先级) | 优先级低于.normal的请求(如预取)最多占用 3 个槽 |
imageDecodingQueue | 2 | 仅异步解码器在此运行 |
imageEncodingQueue | 1 | — |
imageProcessingQueue | 2 | — |
imageDecompressingQueue | 2 | — |
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 的速查表
- 抬升系统与工具链版本:iOS 15.0 / tvOS 15.0 / macOS 12.0 / watchOS 8.0 / visionOS 1.0,Xcode 26.0,Swift 6.2。
- 收敛错误处理:
image(for:)、data(for:)、ImageTask.image/response现在只抛ImagePipeline.Error,删除旧的catch let error as ImagePipeline.Error+ 兜底catch双重分支;新增的.cancelled建议用error.isCancelled过滤。 - 替换 userInfo 键:
imageIdKey→request.imageID(注意大小写,且现在是可读写属性),scaleKey→request.scale,thumbnailKey→request.thumbnail。 - 重命名 Delegate:
ImagePipelineDelegate→ImagePipeline.Delegate;移除旧的逐事件 delegate 方法,统一到imageTask(_:didReceiveEvent:pipeline:)。 - 改写取消事件:
Event.cancelled→Event.finished(.failure(.cancelled));如需任务开始事件,使用新增的Event.started。 - 检查队列配置:
callbackQueue、dataCachingQueue已移除;OperationQueue相关配置迁移到TaskQueue的同名 API,注意配置复制共享队列的语义。 - 审视新增默认限制:确认
maximumResponseDataSize(默认内存 10%、上限 200 MB)是否符合业务,不需要限制则设为nil;progressiveDecodingInterval默认 0.5 秒,可按需调整。 - 适配主线程闭包:确认传入的闭包符合
@MainActor @Sendable约束,必要时为self捕获补充[weak self]或让类型主 actor 隔离。
完成上述八项后,你的工程即可平稳运行在 Nuke 13 上。若在迁移中遇到具体符号无法解析,可对照 Deprecated.swift 中的 unavailable 桩——编译器错误信息里通常已写明推荐的替换 API 或 fix-it。
- 移动开发
- 图像处理
【免费下载链接】Nuke
Image loading system
相关推荐
Nuke 14 迁移指南:从 Nuke 13.x 升级的完整 API 变更与适配方案
Nuke 14 迁移指南:从 Nuke 13.x 升级的完整 API 变更与适配方案 本指南面向正在使用 Nuke 13.x 并准备升级到 Nuke 14 的开
移动开发图像处理50个Web项目TypeScript迁移完整指南:类型安全升级实战教程 🚀
50个Web项目TypeScript迁移完整指南:类型安全升级实战教程 🚀 50projects50days是一个包含50个迷你Web项目的学习资源库,涵盖了
示例工程前端Zustand v4 迁移指南:从 v3 升级的类型系统全面详解
Zustand v4 迁移指南:从 v3 升级的类型系统全面详解 导读 :Zustand 4.0 是一次"运行时零破坏、类型层大重构"的版本升级——唯一的 br
前端
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考