What's Changed
- Fix
GaussianBlurcrash 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
- Fix a leak where requests that finish synchronously – memory cache hits, disk cache hits for
data(for:), local resources,.returnCacheDataDontLoadmisses, and malformed URLs – were never removed from the pipeline, retaining their responses for its lifetime - Fix
ImageTask.Event.startedbeing delivered toImagePipelineDelegateafter.finishedfor requests that finish synchronously - Fix a data race in
ImageDecoderRegistrywheredecoder(for:)iterated the registered decoders without holding the lock. The registry is now backed byMutexand isSendableinstead of@unchecked Sendable; the closures are called outside of the lock ImageDecoderRegistry.register(_:)now takes a@Sendableclosure. The registry is shared between the pipelines and the closures are called on the decoding threads, so registrations capturing non-Sendablestate now fail to compile instead of silently escaping the check- Add
ImageDecoderRegistry.unregister(_:).register(_:)now returns a discardableImageDecoderRegistry.RegistrationTokenfor removing an individual decoder instead of clearing the entire registry - Fix
DataLoadercallingcompletiontwice when response validation fails, violating theDataLoadingcontract. The handler is now unregistered before the task is cancelled, so theURLError(.cancelled)fromdidCompleteWithErrorno longer follows the validation error - Fix a request cancelled while
ImagePipelineDelegate/willLoadData(for:urlRequest:pipeline:)was suspended still running the download to completion, discarding the result and occupying a slot indataLoadingQueue. The pipeline now re-checks for cancellation when the delegate returns - Fix resumable data being lost when a request ended before the server responded. The pipeline takes the data out of the storage before loading and now puts it back if nothing new was downloaded
- Fix requests with
ImageRequest/Options/skipDataLoadingQueuenot cancelling the underlyingTask, leaving an asyncwillLoadDataor a customdatafetch closure running after cancellation - Fix the async/await variant of
FetchImage/load(_:)delivering a result after the load was cancelled or superseded.Taskcancellation is cooperative, so an action that doesn't check for it runs to completion – its result is now discarded instead of overwriting the state of the newer load, flippingisLoadingoff mid-load, and callingonCompletion - Fix
LazyImagewith theLazyImage/DisappearBehavior/lowerPrioritydisappear behavior permanently lowering the priority of its requests after the view goes off-screen. The override wasn't reset when the view reappeared, so every subsequent load ran with.veryLowpriority, ignoringLazyImage/priority(_:) - Fix
LazyImageViewstacking the views created byLazyImageView/makeImageView. A view was added for every displayed response – each progressive preview, the cached preview, and the final image – without removing the previous one, so the orphaned views survived cell reuse, covered the newer content, and retained their images - Fix
ImagePrefetcher/stopPrefetching(with:)andImagePrefetcher/stopPrefetching()doing nothing for the prefetches that were scheduled but haven't started yet. In the standard collection view pattern –startPrefetchingimmediately followed bystopPrefetching– the requests ran the entire download/decode/cache cycle anyway. It also affected the requests cancelled when the prefetcher is deallocated, and the priority updates - Fix
ImageDecoders/Emptynot marking the containers returned bydecodePartiallyDownloadedData(_:)as previews. Downstream checks treated the truncated data as a final image, so it was stored in the memory cache regardless ofImagePipeline/Configuration/isStoringPreviewsInMemoryCache, encoded into the data cache withImagePipeline/DataCachePolicy/storeEncodedImages, and delivered as a completed response on the subsequent cache hits - Fix
ImageProcessors/CoreImageFiltersharing a single mutableCIFilterbetween concurrently processed images.CIFilteris not thread-safe – the processor sets the input image and then reads the output – so a processor created withImageProcessors/CoreImageFilter/init(_:identifier:)could produce an image from another request's input and cache it under the wrong key.ImageProcessors/CoreImageFilter/apply(filter:to:)now applies a copy, leaving the original untouched - Fix the screen scale used by
ImageProcessingOptions/Unit/pointsbeing read once and cached for the lifetime of the process.UITraitCollection.currentis only populated in the contexts managed by UIKit and reports a0display scale everywhere else, so if the first points-basedImageRequestwas created on a background thread, every subsequent resize computed a zero target size and failed withImagePipeline/Error/processingFailed(processor:context:error:), and the point-based border widths and corner radii became0. The scale is now re-evaluated on every access, falling back to the last known valid value - Fix progressive previews being permanently disabled when the first downloaded chunk isn't enough to tell that the image supports them. The decoder is created from the first chunk and configured with the preview policy computed for it, but
ImagePipeline/PreviewPolicy/default(for:)returnsImagePipeline/PreviewPolicy/disableduntil Image I/O can parse the frame header – which for progressive JPEGs with large EXIF/ICC preambles doesn't happen until well into the download, producing zero previews. The policy is now re-evaluated as more data arrives and the decoder is replaced if it changes. Re-evaluation stops as soon as the policy produces previews, requires the data to at least double, and has a hard cap, so the pipeline no longer parses the partially downloaded data on every chunk - Fix
ImagePrefetcher/prioritynot reaching the prefetches that were scheduled but haven't started yet. The new priority was applied to the queue slot and the existingImageTaskinstances, but the pending prefetches kept the priority frozen in their original requests, so they eventually did the actual work – data loading, decoding, processing – at the old priority - Fix a crash on macOS when an
ImageProcessingOptions/Borderis created with anNSColoroutside an RGB colorspace – including.black,.white, the grays, and the catalog colors such as.labelColor. UnlikeUIColor,NSColordoesn't convert on the fly and raises anNSInvalidArgumentExceptionfromgetRed(_:green:blue:alpha:), so merely readingImageProcessors/Circle/identifierorImageProcessors/RoundedCorners/identifier– which happens on every request when a data cache is configured – terminated the process. The color is now converted to sRGB first
New Contributors
- @Delarkz made their first contribution in #880
- @ZHUOLIN0928 made their first contribution in #881
- @thliu21 made their first contribution in #879
Full Changelog: 13.0.6...13.1.0