Releases: kean/Nuke
Nuke 13.0.1
Nuke 13.0
Nuke 13 achieves full Data Race Safety by migrating all 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 options.
Requirements
- Minimum supported Xcode version: 26.0.
- Minimum required platforms: iOS 15.0, watchOS 8.0, macOS 12.0, tvOS 13.0, visionOS 1.0
Quality
The test suite was rewritten in Swift Testing with Swift 6 mode enabled and significantly expanded. Despite the additional tests, the suite is 3x faster thanks to the parallelization.
| Version | Source lines | Tests | Test lines | Coverage | Test Time |
|---|---|---|---|---|---|
| Nuke 13.0 | 4,669 | 768 | 8,509 | 96.0% | 1.3s |
| Nuke 12.9 | 4,589 | 496 | 6,167 | 92.4% | 3.5s |
Concurrency & Data Race Safety
The codebase is migrated to Swift Concurrency and supports Data Race Safety, with some minor exceptions that will be addressed in the future iterations.
- Replace the internal serial
DispatchQueuewith a@globalActor(ImagePipelineActor) for pipeline synchronization, making thread-safety compiler-enforced. The actor is public so that customImagePipeline.Delegateimplementations can use it when needed to reduce thread hops - Replace
OperationQueue-based scheduling with a customTaskQueuesynchronized onImagePipelineActor. 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 - Add typed throws (
throws(ImagePipeline.Error)) toImageTask.image,ImageTask.response,ImagePipeline.image(for:), andImagePipeline.data(for:). AddImagePipeline.Error.cancelledcase. Cancellation now throws this instead ofCancellationError. - Change
userInfotype from[UserInfoKey: Any]to[UserInfoKey: any Sendable]in bothImageRequestandImageContainer - Add
@MainActor @Sendableto completion-basedloadImage/loadDataclosure parameters - Add
@MainActor @Sendabletoprogressandcompletionclosures inNukeExtensionsloadImagefunctions - Add
@MainActor @Sendableto all callback closures inNukeUI:FetchImage.onStart/onCompletion,LazyImage.onStart/onCompletionmodifiers,LazyImageView.onStart/onPreview/onProgress/onSuccess/onFailure/onCompletion - Eliminate an actor hop during
ImageTaskstartup, reducing per-request overhead - Synchronize
ResumableDataStorageonImagePipelineActor, replacingNSLockwith 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:.incrementalfor progressive JPEGs and GIFs,.disabledfor everything else (baseline JPEGs, PNGs, etc.), restoring the original behavior beforeCGImageSourceCreateIncrementalwas adopted - Add
ImagePipeline.Delegate.willLoadData(for:urlRequest:pipeline:), an async, throwing hook that intercepts theURLRequestjust 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 anImageContainerdirectly. 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 inTaskFetchOriginalImage– #823 - Add type-safe
imageID,scale, andthumbnailproperties toImageRequest, replacing the previoususerInfodictionary-based approach. The new properties are more ergonomic and improve performance by eliminating dictionary lookups andAnyboxing. TheuserInfo[.imageIdKey],userInfo[.scaleKey], anduserInfo[.thumbnailKey]keys are deprecated. The newimageIDproperty replacesimageIdto 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 tonilto 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 tonilto disable - Add
DataCache.isSweepEnabled(trueby default). Set it tofalsein 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.icowith 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.GaussianBlurto 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.defaultCostLimitto 15% of physical memory and a hard cap of 768 MB (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
ResumableDataStorageis now dynamic and varies depending on the available RAM. - Add
consumingtoLazyImagebuilder methods (processors,priority,pipeline,onStart,onDisappear,onCompletion) andImageContainer.map(_:)
API Changes
- Rename
ImagePipelineDelegatetoImagePipeline.Delegate. A deprecatedImagePipelineDelegatetypealias is provided for backward compatibility - Refactor
ImageDecoders.Defaultto fully delegate incremental decoding to Image I/O viaCGImageSourceCreateIncremental - Remove
queueparameter from completion-basedloadImage/loadDatamethods — callbacks now always run on the main queue - Remove
ImageTask.Event.cancelledin favor of.finished(.failure(.cancelled))— cancellation is now uniformly represented as a failure result - Remove
ImageRequest.init(id:dataPublisher:)and internalTaskFetchWithPublisher. UseImageRequest.init(id:data:)(async closure) instead — it is now handled directly byTaskFetchOriginalData - Remove soft-deprecated per-event
ImagePipelineDelegatemethods (imageTaskDidStart,didUpdateProgress,didReceivePreview,imageTaskDidCancel,didCompleteWithResult). UseimageTask(_:didReceiveEvent:pipeline:)instead - Remove previously deprecated APIs:
DataCache.isCompressionEnabled,ImageProcessors.Resize.ContentModetypealias,AsyncImageTasktypealias,ImagePipeline.Configuration.callbackQueue,ImagePipeline.Configuration.dataCachingQueue,ImagePipeline.loadData(with: URL), andImagePipeline.data(for: URL) - Soft-deprecate the
userInfoparameter inImageRequestinitializers in favor of dedicated type-safe properties
Bug Fixes
- Fix progressive JPEGs with large EXIF headers not producing previews —
CGImageSourceCreateIncrementalfails 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
- Fix
ImageTask.stateremaining.runningafter completion when using the completion-basedloadImageAPI - Fix
ImageDecoders.Video.decode(_:)returning an empty image instead of a video thumbnail — #811 - Fix
VideoPlayerViewaccumulating duplicateAVPlayerItemDidPlayToEndTimeobservers on eachplay()/reset()cycle, causingonVideoFinishedto fire multiple times — #818
New Contributors
- @theop-luma made their first contribution in #835
Nuke 13.0 (Beta 2)
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.
Requirements
- Minimum supported Xcode version: 26.0.
- Minimum required platforms: iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15.
Quality
- Convert unit tests to Swift Testing and enable Swift 6 mode for all tests and expand test coverage
| 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% |
Concurrency & Data Race Safety
- Replace the internal serial
DispatchQueuewith a@globalActor(ImagePipelineActor) for pipeline synchronization, making thread-safety compiler-enforced. The actor is public so that customImagePipeline.Delegateimplementations can use it when needed to reduce thread hops - Replace
OperationQueue-based scheduling with a customTaskQueuesynchronized onImagePipelineActor. 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
DataLoadingprotocol with async/await:loadData(with:)now returns(AsyncThrowingStream<Data, Error>, URLResponse). RemoveCancellableprotocol - Add typed throws (
throws(ImagePipeline.Error)) toImageTask.image,ImageTask.response,ImagePipeline.image(for:), andImagePipeline.data(for:). AddImagePipeline.Error.cancelledcase. Cancellation now throws this instead ofCancellationError. - Change
userInfotype from[UserInfoKey: Any]to[UserInfoKey: any Sendable]in bothImageRequestandImageContainer - Add
@MainActor @Sendableto completion-basedloadImage/loadDataclosure parameters - Add
@MainActor @Sendabletoprogressandcompletionclosures inNukeExtensionsloadImagefunctions - Add
@MainActor @Sendableto all callback closures inNukeUI:FetchImage.onStart/onCompletion,LazyImage.onStart/onCompletionmodifiers,LazyImageView.onStart/onPreview/onProgress/onSuccess/onFailure/onCompletion - Eliminate an actor hop during
ImageTaskstartup, reducing per-request overhead - Synchronize
ResumableDataStorageonImagePipelineActor, replacingNSLockwith actor isolation and removing@unchecked Sendable.
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:.incrementalfor progressive JPEGs and GIFs,.disabledfor everything else (baseline JPEGs, PNGs, etc.), restoring the original behavior beforeCGImageSourceCreateIncrementalwas adopted - Add
ImagePipeline.Delegate.willLoadData(for:urlRequest:pipeline:), an async, throwing hook that intercepts theURLRequestjust 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 anImageContainerdirectly. 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 inTaskFetchOriginalImage– #823 - Add type-safe
imageID,scale, andthumbnailproperties toImageRequest, replacing the previoususerInfodictionary-based approach. The new properties are more ergonomic and improve performance by eliminating dictionary lookups andAnyboxing. TheuserInfo[.imageIdKey],userInfo[.scaleKey], anduserInfo[.thumbnailKey]keys are deprecated. The newimageIDproperty replacesimageIdto 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 tonilto 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 tonilto disable - Add
DataCache.isSweepEnabled(trueby default). Set it tofalsein 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.icowith 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.GaussianBlurto 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.defaultCostLimitto 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
ResumableDataStorageis now dynamic and varies depending on the available RAM. - Add
consumingtoLazyImagebuilder methods (processors,priority,pipeline,onStart,onDisappear,onCompletion) andImageContainer.map(_:)
API Changes
- Rename
ImagePipelineDelegatetoImagePipeline.Delegate. A deprecatedImagePipelineDelegatetypealias is provided for backward compatibility - Refactor
ImageDecoders.Defaultto fully delegate incremental decoding to Image I/O viaCGImageSourceCreateIncremental - Remove
queueparameter from completion-basedloadImage/loadDatamethods — callbacks now always run on the main queue - Remove
ImageTask.Event.cancelledin favor of.finished(.failure(.cancelled))— cancellation is now uniformly represented as a failure result - Remove
ImageRequest.init(id:dataPublisher:)and internalTaskFetchWithPublisher. UseImageRequest.init(id:data:)(async closure) instead — it is now handled directly byTaskFetchOriginalData - Remove soft-deprecated per-event
ImagePipelineDelegatemethods (imageTaskDidStart,didUpdateProgress,didReceivePreview,imageTaskDidCancel,didCompleteWithResult). UseimageTask(_:didReceiveEvent:pipeline:)instead - Remove previously deprecated APIs:
DataCache.isCompressionEnabled,ImageProcessors.Resize.ContentModetypealias,AsyncImageTasktypealias,ImagePipeline.Configuration.callbackQueue,ImagePipeline.Configuration.dataCachingQueue,ImagePipeline.loadData(with: URL), andImagePipeline.data(for: URL) - Soft-deprecate the
userInfoparameter inImageRequestinitializers in favor of dedicated type-safe properties
Bug Fixes
- Fix progressive JPEGs with large EXIF headers not producing previews —
CGImageSourceCreateIncrementalfails 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
Nuke 12.9
What's Changed
- Enable Swift 6 and fix remaining concurrency warnings
- Optimize
ImageTaskAsyncStreamAPIs and remove the Combine dependency. It now essentially has no overhead. - Updating misleading SVG support by @realmtai in #839
- Fix deprecation warning typo by @cameronmcefee in #861
- Mark
DataLoadingclosures as@Sendableby @plu in #862 .storeAllnow stores processed images for locals too, as it should be by @HyperfocusDisordered in #810- Add
.heicsupport toAssetType/initso it can detect it based on the inputData - Remove some
@uncheckedmarkers fromSendabletypes for better Data Race Safety checking - Fix an issue with
DateCachenot touching.contentAccessDatewhen accessing files
New Contributors
- @realmtai made their first contribution in #839
- @cameronmcefee made their first contribution in #861
- @plu made their first contribution in #862
- @HyperfocusDisordered made their first contribution in #810
Nuke 13.0 (Beta 1)
Nuke 13 is an incremental release that takes more advantage of Swift Concurrency and Data Race Safety by introducing a global ImagePipelineActor on which its subsystems are all synchronized. It ensures thread-safety, removes 20 usages of @unchecked Sendable, and makes the framework even leaner with ~5% less code in the main module. The future versions will completely switch to Swift Concurrency, but it will require a couple of more iterations.
Requirement: Xcode 16.
- Increase deployment targets to iOS 14, tvOS 14, macOS 11, watchOS 7
- Add global actor
ImagePipelineActor.ImagePipeline,ImageTask,ImagePrefetcher, and some other internal types are synchronized on the new actor. - Soft-deprecate closure-based
ImagePipelineAPIs - Soft-deprecate
ImagePipelineCombine extension and move them toNukeExtensions - Remove
ImagePipeline.Configuration.callbackQueue - Remove
queueparameter fromImagePipelineloadImageandloadDatamethod. The callbacks are now isolated to the@MainActorand are@Sendable. - Add
ImageTask.isCancellingto make it easier to transition to Nuke 13 in case you were usingImageTask.statefor checking for cancellation invoked by the client (this might change in the upcoming betas) - Rename
ImagePipelineDelegatetoImagePipeline.Delegate - Remove
ImagePipeline.DelegateimageTaskDidStartand other methods soft-deprecated in Nuke 12.7. - Remove
ImageRequestinitializers acceptingdataPublisher(Combine) – use Swift Concurrency instead - Update
DataLoadingprotocol to use Swift Concurrency instead of closures - Deprecate
FetchImageCombine support
Nuke 12.8
What's Changed
- Add Swift 6 compatibility
- Add
@MainActorannotation toImageLoadingOptions.shared - Fix image scale and orientation issue in thumbnail creation by @prabhuamol in #793
- Deprecate
ImagePipeline.Configuration.callbackQueue– this feature will be removed in Nuke 13 ImagePrefetcher.didCompleteclosure is now annotated with@MainActor @Sendable- Drop Xcode 14 support
New Contributors
- @prabhuamol made their first contribution in #793
Nuke 12.8 (Beta 1)
- Deprecate
ImagePipeline.Configuration.callbackQueue– this feature will be removed in Nuke 13 ImagePrefetcher.didCompleteclosure is now annotated with@MainActor @Sendable- Add
@MainActorannotation toImageLoadingOptions.shared - Drop Xcode 14 support
Nuke 12.7.3
Nuke 12.7.2
- Fix #789, an issue with
ImageProcessors.Resizefailing to resize images with packed pixel formats