How to call completion handler after async function?

Overview:

  • There is a protocol GarageFetchable defined in a framework
  • I have no control over this framework.
  • Protocol contains a function fetchGarage that accepts a completion handler

Problem:

  • I would like to execute a swift concurrent function and then call the completion handler
  • When I call the completion and pass value I get the warning: Capture of 'completion' with non-sendable type '(Garage) -> ()' in a @Sendable closure

Environment:

  • I have set the build setting Strict Concurrency Checking to Complete
  • Xcode 14.0 beta 6 (14A5294g)
  • macOS 13.0 Beta (22A5331f)

Question:

  • What is the right way / approach to call the completion handler after an async function?

Code:

struct Car {
    var name: String
}

struct Garage {
    let name: String
    let cars: [Car]
}

func fetchCars() async throws -> [Car] {
    try await Task.sleep(nanoseconds: 2_000_000_000)
    let cars = [Car(name: "aaa"),
                Car(name: "bbbb")]
    return cars
}

//This is defined in the framework and I have no control over this function
protocol GarageFetchable {
    func fetchGarage(completion: @escaping (Garage) -> ())
}

struct DataStore: GarageFetchable {
    func fetchGarage(completion: @escaping (Garage) -> ()) {
        Task {
            let cars = try? await fetchCars()
            let garage = Garage(name: "Garage1", cars: cars ?? [])
            completion(garage) //Warning: Capture of 'completion' with non-sendable type '(Garage) -> ()' in a `@Sendable` closure
        }
    }
}

We don't currently have a better answer than just calling the completion handler from the Task you created. Hopefully you can encourage this framework to adopt async/await sooner rather than later. At the very least, it should declare the completion handler function as @Sendable, which would eliminate that warning.

We are hoping to do some implementation work to preserve structured concurrency for this pattern across @objc interfaces, but it probably will not be straightforward to take advantage of that in your code, especially if it's doing completion handlers with native Swift function types instead of ObjC blocks.

1 Like

@John_McCall Thanks a lot for the clarification on this.

This is part of the Apple's WidgetKit framework getsnapshot(in:completion:)

Note: I had created a sample function to illustrate the problem, original function is from the protocol TimelineProvider

Should I be filing a feedback for this?

Yes, please do.

1 Like

Thanks a lot @John_McCall

Feedback ID: FB11428998 contains sample project and steps to reproduce

1 Like