Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Crash when executing a request on a custom background queue and compiling with Swift 6 language mode #828

Open
marcelomp opened this issue Jan 17, 2025 · 0 comments

Comments

@marcelomp
Copy link

Steps to reproduce:

  1. Compile with Xcode 16 and Swift 6 language mode
  2. Execute an ImageRequest on a background callback queue
  3. Checks that it crashes with a _dispatch_assert_queue_fail ()

Obs 1.: If the queue is main (no queue provided nil or explicitly passing .main) or compiled with Swift 5 languange mode it works as expected
Obs 2.: If we wrap the call on a continuation to make a async version for some reason it works

Image

Sample Code

// ContentView.swift

import SwiftUI

import Nuke

struct ContentView: View {
    @StateObject private var state = ContentViewState()
    
    var body: some View {
        VStack {
            if let error = state.error {
                Text(error.localizedDescription)
                    .font(.title)
                    .foregroundStyle(.red)
                    .padding()
                
            } else if let image = state.image {
                Image(uiImage: image)
                    .resizable()
                    .aspectRatio(contentMode: .fit)
                
            } else {
                Text("Empty...")
            }
            
            Button("Fetch") {
                state.fetch()
            }
            .buttonStyle(.borderedProminent)
            .controlSize(.large)
        }
    }
}

@MainActor
final class ContentViewState: ObservableObject {
    @Published var image: UIImage?
    @Published var error: Error?
    
    static let imageUrl = URL(string: "https://upload.wikimedia.org/wikipedia/commons/4/47/Caesar.jpg")!
    
    func fetch() {
        // Crashes
        doFetch { result in
            switch result {
            case let .success(image):
                self.image = image
            case let .failure(error):
                self.error = error
            }
        }
        
        // Doesn't crash
//        Task {
//            do {
//                let image = try await doFetch()
//                self.image = image
//                
//            } catch {
//                self.error = error
//            }
//        }
    }
    
    @discardableResult
    func doFetch(completion: @escaping (Result<UIImage, any Error>) -> Void) -> ImageTask? {
        var task: ImageTask?
        
        let imageRequest = ImageRequest(url: Self.imageUrl)
        
        task = ImagePipeline.shared.loadImage(with: imageRequest, queue: .global(), progress: nil) { result in
            switch result {
            case let .success(response):
                completion(.success(response.image))                
            case let .failure(error):
                completion(.failure(error))
            }
        }
        
        return task
    }
    
    func doFetch() async throws -> UIImage {
        let imageRequest = ImageRequest(url: Self.imageUrl)
        let response = try await ImagePipeline.shared.loadImage(with: imageRequest, queue: .global())
        
        return response.image
    }
}

extension ImagePipeline {
    func loadImage(with request: ImageRequest, queue: DispatchQueue?) async throws -> ImageResponse {
        try await withCheckedThrowingContinuation { continuation in
            loadImage(with: request, queue: queue, progress: nil) { result in
                continuation.resume(with: result)
            }
        }
    }
}

#Preview {
    ContentView()
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant