github kean/Nuke 13.0.0-beta.2
Nuke 13.0 (Beta 2)

pre-release9 hours ago

Nuke 13 achieves Data Race Safety by migrating pipeline work to Swift Concurrency, replacing DispatchQueue and OperationQueue with a @globalActor-based synchronization model. It also ships over 10 new APIs, including progressive preview policies, a willLoadData auth hook, memory size limits, and type-safe ImageRequest properties.

The test suite was rewritten in Swift Testing with Swift 6 mode enabled and significantly expanded:

Version Source lines Tests Test lines Coverage
Nuke 13.0 4,669 768 8,509 96.0%
Nuke 12.9 4,589 496 6,167 92.4%

Requirements

  • Minimum supported Xcode version: 26.0.
  • Minimum required platforms: iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15.

Concurrency & Data Race Safety

  • Replace the internal serial DispatchQueue with a @globalActor (ImagePipelineActor) for pipeline synchronization, making thread-safety compiler-enforced. The actor is public so that custom ImagePipeline.Delegate implementations can use it when needed to reduce thread hops
  • Replace OperationQueue-based scheduling with a custom TaskQueue synchronized on ImagePipelineActor. Background operations like image processing and decoding now run on the default Swift Concurrency executors, eliminating unnecessary thread hops. The entire pipeline is now a good Swift Concurrency citizen
  • Replace callback-based DataLoading protocol with async/await: loadData(with:) now returns (AsyncThrowingStream<Data, Error>, URLResponse). Remove Cancellable protocol
  • Add typed throws (throws(ImagePipeline.Error)) to ImageTask.image, ImageTask.response, ImagePipeline.image(for:), and ImagePipeline.data(for:). Add ImagePipeline.Error.cancelled case. Cancellation now throws this instead of CancellationError.
  • Change userInfo type from [UserInfoKey: Any] to [UserInfoKey: any Sendable] in both ImageRequest and ImageContainer
  • Add @MainActor @Sendable to completion-based loadImage/loadData closure parameters
  • Add @MainActor @Sendable to progress and completion closures in NukeExtensions loadImage functions
  • Add @MainActor @Sendable to all callback closures in NukeUI: FetchImage.onStart/onCompletion, LazyImage.onStart/onCompletion modifiers, LazyImageView.onStart/onPreview/onProgress/onSuccess/onFailure/onCompletion
  • Eliminate an actor hop during ImageTask startup, reducing per-request overhead
  • Synchronize ResumableDataStorage on ImagePipelineActor, replacing NSLock with actor isolation and removing @unchecked Sendable.
  • Convert unit tests to Swift Testing and enable Swift 6 mode for all tests

New Features

  • Add ImagePipeline.PreviewPolicy (.incremental, .thumbnail, .disabled) to control how progressive previews are generated per-request
  • Add ImagePipelineDelegate.previewPolicy(for:pipeline:) for customizing the policy dynamically. Default policy: .incremental for progressive JPEGs and GIFs, .disabled for everything else (baseline JPEGs, PNGs, etc.), restoring the original behavior before CGImageSourceCreateIncremental was adopted
  • Add ImagePipeline.Delegate.willLoadData(for:urlRequest:pipeline:), an async, throwing hook that intercepts the URLRequest just before data loading begins. Use it to inject auth tokens, sign requests, or perform any async pre-flight work. Throw to cancel with a meaningful error (e.g., when a token refresh fails). Default implementation returns the request unchanged — #774
  • Add ImageRequest.init(id:image:) that accepts an async closure returning an ImageContainer directly. Use it to process images already in memory or to integrate with systems that provide pre-decoded images (e.g., Photos framework). The image skips data decoding entirely and is loaded in TaskFetchOriginalImage#823
  • Add type-safe imageID, scale, and thumbnail properties to ImageRequest, replacing the previous userInfo dictionary-based approach. The new properties are more ergonomic and improve performance by eliminating dictionary lookups and Any boxing. The userInfo[.imageIdKey], userInfo[.scaleKey], and userInfo[.thumbnailKey] keys are deprecated. The new imageID property replaces imageId to follow idiomatic Swift naming (uppercase "ID") and is now also writable – #772
  • Add ImagePipeline.Configuration.progressiveDecodingInterval (default: 0.5s) to throttle progressive decoding attempts when data arrives faster than the interval
  • Add ImagePipeline.Configuration.maximumResponseDataSize — downloads that exceed this limit are automatically cancelled. The default limit is based on the device's physical memory. Set to nil to disable — #738
  • Add ImagePipeline.Configuration.maximumDecodedImageSize — images whose decoded bitmap would exceed this limit are automatically downscaled during decoding. The default limit is calculated dynamically based on the device's physical memory. Set to nil to disable
  • Add DataCache.isSweepEnabled (true by default). Set it to false in targets that share a cache with the main app (e.g. a Notification Service Extension) so that only the main app enforces size limits via LRU sweeps
  • Add AssetType.ico with magic-byte detection for ICO (Windows icon) images
  • Add ImageTask.Event.started
  • Mark all public enums as @frozen (except error enums and empty namespaces)

Performance

  • Rewrite ImageProcessors.GaussianBlur to use Accelerate (vImageBoxConvolve) instead of Core Image, fixing gray border artifacts and improving performance ~5.8x — #308
  • Optimize data downloading by pre-allocating the buffer using the expected content size from the HTTP response, reducing memory reallocations during image downloads (this only applies when progressive decoding is on) — #738
  • Update ImageCache.defaultCostLimit to 15% of physical memory with no hard cap (previously 20% capped at 512 MB). The cache uses a custom LRU policy that enforces limits precisely, so 15% is effectively more generous than the previous capped value on modern devices – #838
  • The storage cost limit of ResumableDataStorage is now dynamic and varies depending on the available RAM.
  • Add consuming to LazyImage builder methods (processors, priority, pipeline, onStart, onDisappear, onCompletion) and ImageContainer.map(_:)

API Changes

  • Rename ImagePipelineDelegate to ImagePipeline.Delegate. A deprecated ImagePipelineDelegate typealias is provided for backward compatibility
  • Refactor ImageDecoders.Default to fully delegate incremental decoding to Image I/O via CGImageSourceCreateIncremental
  • Remove queue parameter from completion-based loadImage/loadData methods — callbacks now always run on the main queue
  • Remove ImageTask.Event.cancelled in favor of .finished(.failure(.cancelled)) — cancellation is now uniformly represented as a failure result
  • Remove ImageRequest.init(id:dataPublisher:) and internal TaskFetchWithPublisher. Use ImageRequest.init(id:data:) (async closure) instead — it is now handled directly by TaskFetchOriginalData
  • Remove soft-deprecated per-event ImagePipelineDelegate methods (imageTaskDidStart, didUpdateProgress, didReceivePreview, imageTaskDidCancel, didCompleteWithResult). Use imageTask(_:didReceiveEvent:pipeline:) instead
  • Remove previously deprecated APIs: DataCache.isCompressionEnabled, ImageProcessors.Resize.ContentMode typealias, AsyncImageTask typealias, ImagePipeline.Configuration.callbackQueue, ImagePipeline.Configuration.dataCachingQueue, ImagePipeline.loadData(with: URL), and ImagePipeline.data(for: URL)
  • Soft-deprecate the userInfo parameter in ImageRequest initializers in favor of dedicated type-safe properties

Bug Fixes

  • Fix progressive JPEGs with large EXIF headers not producing previews — CGImageSourceCreateIncremental fails to recognize these files until fully downloaded. The decoder now falls back to generating a thumbnail from a non-incremental source. The issue was raised by and the initial fix provided by @theop-luma in #835
  • Fix thumbnail requests re-downloading original image data when it is already stored in the disk cache — #837

Don't miss a new Nuke release

NewReleases is sending notifications on new releases.