From 101fc7573b45c58b581c5c0bf3ce97dbd9ba1e6c Mon Sep 17 00:00:00 2001 From: Stefan Ceriu Date: Mon, 22 Jun 2026 14:18:53 +0300 Subject: [PATCH] Don't crash when the GIF data provider is invoked off the main thread LoadableImageContent.data(handler:) used MainActor.assumeIsolated to read the loader's content, which crashes (libdispatch precondition) when Kingfisher invokes the provider off the main thread, and fatalError'd when the content wasn't GIF data. Hop onto the main actor and return a failure instead. The handler is @Sendable per Kingfisher's protocol. --- .../Other/SwiftUI/Views/LoadableImage.swift | 23 ++++++++++--------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/ElementX/Sources/Other/SwiftUI/Views/LoadableImage.swift b/ElementX/Sources/Other/SwiftUI/Views/LoadableImage.swift index cd93debe1..be7ef31d2 100644 --- a/ElementX/Sources/Other/SwiftUI/Views/LoadableImage.swift +++ b/ElementX/Sources/Other/SwiftUI/Views/LoadableImage.swift @@ -234,23 +234,24 @@ private struct LoadableImageContent) -> Void) { - // Kingfisher isn't annotated but invokes the provider on the main thread. - let gifData = MainActor.assumeIsolated { () -> Data? in + nonisolated func data(handler: @escaping @Sendable (Result) -> Void) { + // Kingfisher isn't annotated and doesn't guarantee the provider is invoked on the main + // thread so hop onto the main actor. + Task { @MainActor in guard case let .gifData(data) = contentLoader.content else { - return nil + handler(.failure(LoadableImageError.missingGIFData)) + return } - return data + + handler(.success(data)) } - - guard let gifData else { - fatalError("Shouldn't reach this point without any gif data") - } - - handler(.success(gifData)) } } +private enum LoadableImageError: Error { + case missingGIFData +} + private class ContentLoader: ObservableObject { enum Content: Equatable { case image(UIImage)