Skip to content

Releases: kean/Nuke

Nuke 13.2.0

Choose a tag to compare

@kean kean released this 15 Aug 17:08

What's Changed

  • Add AssetType/utType and AssetType/init(_:) for bridging to and from UTType
  • Add AssetType/bmp, AssetType/tiff, AssetType/jpeg2000, and AssetType/jxl. Image I/O decodes these formats, but AssetType/init(_:) didn't recognize them, so ImageContainer/type was nil
  • Fix AssetType/init(_:) detecting GIF from the first three bytes instead of the complete GIF87a/GIF89a signature, which kept the original data in the memory cache for the files that merely started with GIF
  • Fix AssetType/webp, AssetType/mp4, AssetType/m4v, and AssetType/mov using the identifiers that the system doesn't declare, so they didn't bridge to a UTType
  • Fix MP4 files with the standard mp42 major brand being detected as AssetType/m4v
  • Detect the ISO base media types from the major brand in the ftyp box, which adds the remaining MP4, M4V, and HEIC brands
  • Fix progressive image downloads not completing when isResetEnabled is true on LazyImageView by @nathantannar4 in #884
  • Fix ImagePipeline.Cache.storeCachedImage(_:for:caches:) storing image previews in the disk cache, so a progressive scan passed to it was later delivered as a completed image – #885
  • Fix ImageProcessors.Resize ignoring upscale when crop is true, which enlarged the image beyond its native resolution – #886
  • Fix the file and data URL schemes being matched case-sensitively, so FILE:///… and Data:… URLs skipped the local resource fast path and had their contents copied into the data cache – #887
  • Fix a crash when resizing an image to a non-finite size. CGContext creation converted the dimensions with Int(_:), which traps on NaN and infinity – #888
  • Fix ImageDecoders/Default generating GIF previews even when the preview policy is ImagePipeline/PreviewPolicy/disabled – #892
  • Fix ImageProcessors/GaussianBlur blurring the image with a radius of 0 or less instead of returning it unchanged. The radius was clamped to a minimum of 2, so every radius in 0...2 also rendered identically under different cache keys – #893
  • Fix ImageProcessingOptions/Border producing the same identifier for every color with no sRGB representation, e.g. a pattern color. The components are now also clamped to 0...1, which used to produce malformed hex for wide-gamut colors on iOS – #891
  • Fix progressive previews being silently dropped for the rest of the task's lifetime when the cached data fails to decode and the task falls back to loading the image – #894
  • Fix DataLoader/delegate never receiving urlSession(_:didCreateTask:) on macOS and Mac Catalyst, where the forwarding was compiled out – #895
  • Fix ImageProcessors/GaussianBlur tagging opaque images with an alpha channel, which made ImageEncoders/Default encode blurred photos as PNG instead of JPEG or HEIC – #896
  • Fix an outstanding progressive decoding or processing operation not being cancelled when a task fails, leaving it to occupy a queue slot and hold on to the decoded preview until the queue drained past it – #898
  • Fix a late image response reaching the memory cache after its task had already ended, letting a stale progressive scan overwrite the entry – #898

New Contributors

Full Changelog: 13.1.0...13.2.0

Nuke 13.1.0

Choose a tag to compare

@kean kean released this 09 Aug 23:00

What's Changed

  • Fix GaussianBlur crash on grayscale images by @Delarkz in #880
  • Fix cache initialization for mergeable libraries by @ZHUOLIN0928 in #881
  • Add asynchronous image decoding support by @thliu21 in #879
  • Add ImageDecoderRegistry.unregister(_:). register(_:) now returns a discardable RegistrationToken for removing an individual decoder instead of clearing the entire registry
  • ImageDecoderRegistry.register(_:) now takes a @Sendable closure. The registry is shared between the pipelines and the closures are called on the decoding threads
  • Fix a data race in ImageDecoderRegistry where decoder(for:) iterated the registered decoders without holding the lock. The registry is now backed by Mutex and is Sendable instead of @unchecked Sendable
  • Fix a leak where requests that finish synchronously (memory cache hits, disk cache hits for data(for:), local resources, .returnCacheDataDontLoad misses, and malformed URLs) were never removed from the pipeline, retaining their responses for its lifetime
  • Fix ImageTask.Event.started being delivered to ImagePipeline.Delegate after .finished for requests that finish synchronously
  • Fix DataLoader calling completion twice when response validation fails, violating the DataLoading contract
  • Fix a request cancelled while ImagePipeline.Delegate.willLoadData(for:urlRequest:pipeline:) was suspended still running the download to completion, discarding the result and occupying a slot in dataLoadingQueue
  • Fix resumable data being lost when a request ended before the server responded. It's now put back if nothing new was downloaded
  • Fix requests with ImageRequest.Options.skipDataLoadingQueue not cancelling the underlying Task, leaving an async willLoadData or a custom data fetch closure running after cancellation
  • Fix progressive previews being permanently disabled when the first downloaded chunk isn't enough to tell that the image supports them, which is common for progressive JPEGs with large EXIF/ICC preambles. The policy is now re-evaluated as more data arrives, but only until it starts producing previews and with a hard cap on the number of attempts
  • Fix ImageDecoders.Empty not marking the containers returned by decodePartiallyDownloadedData(_:) as previews, so the truncated data was stored in the caches and delivered as a completed response on the subsequent cache hits
  • Fix ImageProcessors.CoreImageFilter sharing a single mutable CIFilter between concurrently processed images. CIFilter is not thread-safe, so the processor could produce an image from another request's input and cache it under the wrong key. It now applies a copy of the filter
  • Fix the screen scale used by ImageProcessingOptions.Unit.points being read once and cached for the lifetime of the process. UITraitCollection.current reports a 0 display scale outside of the contexts managed by UIKit, so if the first points-based ImageRequest was created on a background thread, every subsequent resize failed with processingFailed and the point-based border widths and corner radii became 0
  • Fix a crash on macOS when an ImageProcessingOptions.Border is created with an NSColor outside an RGB colorspace, including .black, .white, and the catalog colors such as .labelColor. Unlike UIColor, NSColor raises from getRed(_:green:blue:alpha:) instead of converting on the fly, so merely reading the processor identifier terminated the process. The color is now converted to sRGB first
  • Fix ImagePrefetcher.stopPrefetching(with:) and ImagePrefetcher.stopPrefetching() doing nothing for the prefetches that were scheduled but haven't started yet. In the standard collection view pattern, the requests ran the entire download/decode/cache cycle anyway
  • Fix ImagePrefetcher.priority not reaching the prefetches that were scheduled but haven't started yet. They kept the priority frozen in their original requests and eventually did the actual work at the old priority
  • Fix the async/await variant of FetchImage.load(_:) delivering a result after the load was cancelled or superseded, overwriting the state of the newer load
  • Fix LazyImage with the lowerPriority disappear behavior permanently lowering the priority of its requests after the view goes off-screen. The override wasn't reset when the view reappeared
  • Fix LazyImageView stacking the views created by makeImageView. A view was added for every displayed response without removing the previous one, so the orphaned views survived cell reuse, covered the newer content, and retained their images

New Contributors

Full Changelog: 13.0.6...13.1.0

Nuke 13.0.6

Choose a tag to compare

@kean kean released this 07 May 20:51
  • Fix crashes on the Swift 5 boundary caused by dynamic actor isolation when invoking Optional.map with closures inferred as @MainActor in LazyImage, LazyImageView, FetchImage, and loadImage(with:into:)

Nuke 13.0.5

Choose a tag to compare

@kean kean released this 03 May 19:06
  • Optimize ImageCache reads, writes, and concurrent access
  • Optimize LazyImageView, LazyImage, and FetchImage performance
  • Fix an issue with a deprecated closure-based ImagePipeline API sometimes invoked after cancellation

Nuke 13.0.4

Choose a tag to compare

@kean kean released this 26 Apr 21:04
  • Add missing @ImagePipelineActor isolation to the new willLoadData method in ImagePipeline.Delegate to avoid thread hops
  • Minor performance improvements

Nuke 13.0.3

Choose a tag to compare

@kean kean released this 26 Apr 15:11
90e17d6
  • Minor performance optimizations
  • Remove a few @unchecked Sendable annotations
  • Deprecate ImagePipeline.Configuration.maximumDecodedImageSize and ImageDecodingContext.maximumDecodedImageSize. The automatic downscaling implementation has been removed; setting these values has no effect. Use ImageRequest.ThumbnailOptions to control decoded image size on a per-request basis

Nuke 13.0.2

Choose a tag to compare

@kean kean released this 15 Apr 01:14
  • Fix ImageDecoders.Default double-applying EXIF orientation when downscaling images that exceed maximumDecodedImageSize
  • Fix the default maximumDecodedImageSize being applied too aggressively

Nuke 13.0.1

Choose a tag to compare

@kean kean released this 29 Mar 17:44
  • Fix ThumbnailOptions double-applying EXIF orientation when createThumbnailWithTransform is enabled – #870
  • Fix Xcode 26.0 compatibility – #871

Nuke 13.0

Choose a tag to compare

@kean kean released this 22 Mar 23:58

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 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
  • 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 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 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
  • Fix ImageTask.state remaining .running after completion when using the completion-based loadImage API
  • Fix ImageDecoders.Video.decode(_:) returning an empty image instead of a video thumbnail — #811
  • Fix VideoPlayerView accumulating duplicate AVPlayerItemDidPlayToEndTime observers on each play()/reset() cycle, causing onVideoFinished to fire multiple times — #818

New Contributors

Nuke 13.0 (Beta 2)

Nuke 13.0 (Beta 2) Pre-release
Pre-release

Choose a tag to compare

@kean kean released this 16 Mar 01:06

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 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.

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