Documentation/Migrations/Nuke 13 Migration Guide.md
This guide eases the transition of the existing apps that use Nuke 12.x to the latest version of the framework.
The minimum supported platforms have been raised.
ImageTask.image, ImageTask.response, ImagePipeline.image(for:), and ImagePipeline.data(for:) now use typed throws (throws(ImagePipeline.Error)). Two new error cases have also been added:
ImagePipeline.Error.cancelled — thrown when a task is cancelled (previously CancellationError)ImagePipeline.Error.dataDownloadExceededMaximumSize — thrown when a download exceeds 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
// ...
}
}
Three userInfo dictionary keys have been replaced with dedicated, type-safe properties on ImageRequest. The old keys are deprecated.
// 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)
Note that the existing imageId property has been renamed to imageID (uppercase "ID") and is now writable. The old imageId is deprecated.
// Before (Nuke 12)
let id: String? = request.imageId // read-only
// After (Nuke 13)
var id: String? = request.imageID // read/write
The userInfo dictionary type has also changed from [UserInfoKey: Any] to [UserInfoKey: any Sendable].
ImagePipelineDelegate has been renamed to ImagePipeline.Delegate and is now defined as a nested type. A deprecated typealias is provided for backward compatibility, but you should update your code.
// Before (Nuke 12)
final class MyDelegate: ImagePipelineDelegate { ... }
// After (Nuke 13)
final class MyDelegate: ImagePipeline.Delegate { ... }
Several soft-deprecated per-event delegate methods have been fully removed. Use imageTask(_:didReceiveEvent:pipeline:) instead:
// 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.cancelled has been removed. Cancellation is now uniformly represented as a failure result. Additionally, a new .started case has been added.
// 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))
}
If you were handling .cancelled explicitly, switch to checking for .finished(.failure(.cancelled)):
// Before (Nuke 12)
case .cancelled:
handleCancellation()
// After (Nuke 13)
case .finished(.failure(.cancelled)):
handleCancellation()
The pipeline's work queues have changed type from OperationQueue to TaskQueue, a new custom type synchronized on ImagePipelineActor. It preserves some of the existing API signatures.
The deprecated callbackQueue and dataCachingQueue properties have been fully removed.
Several new configuration properties have been added:
progressiveDecodingInterval — minimum interval between progressive decoding attempts (default: 0.5s)maximumResponseDataSize — downloads exceeding this limit are cancelled automatically (default: 10% of physical memory, capped at 200 MB)If you previously set no limits and want to preserve that behavior, set this to nil:
configuration.maximumResponseDataSize = nil
All callback closures in the public API are now annotated with @MainActor @Sendable. This is a source-breaking change if you pass closures that are not already main-actor isolated.
ImagePipeline closure-based 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
}
The closures are implicitly @MainActor, so capturing self without [weak self] will now produce a warning if self is not @MainActor. Update your closures accordingly.
NukeUI callbacks (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)
}