Home > Mobile >  How to correctly await a Swift callback closure result
How to correctly await a Swift callback closure result

Time:09-10

In the past when I needed to wait for a Swift callback closure (in this case to the InfluxDB Swift API) to produce a result I used a semaphore to signal the completion.

    func getChargeList(from: Int, until: Int) async -> [Charge] {
        let flux =  """
                    from(bucket: "\(self.bucket)")
                        ...
                    """
        let queryTask = Task { () -> [Charge] in
            **let s = DispatchSemaphore(value: 0)**
            var chargeSessions: [Charge] = []
            self.client.queryAPI.query(query: flux) { response, error in
                if let error = error {
                    print("getChargeList error: \(error)")
                }
                if let response = response {
                    do {
                        ...
                }
                **s.signal()**
            }
            **s.wait()**
            return chargeSessions
        }
        return await queryTask.value
    }

As of Swift 5.7 I get a warning

Instance method 'wait' is unavailable from asynchronous contexts; Await a Task handle instead; this is an error in Swift 6

so time to look at solutions.

Being new to Swift and asynchronous programming I have not come up to a solution to the InfluxDB query API returning immediately and then suspending execution until the query results are returned using the trailing closure. Hopefully just missing something something simple due to my lack of experience so any comments will be appreciated.

I have opened an issue with the InfluxDB Swift API repo to consider using new async/await standard but a workaround or solution ahead of an updated library would be useful to have.

CodePudding user response:

You don't need task. Instead, do this:

  1. Wrap your query in function with callback:
func runQuery(from: Int, until: Int, callback: (Result<[Charge], Error>) -> ()) {
     let flux =  """
                    from(bucket: "\(self.bucket)")
                        ...
                    """
     self.client.queryAPI.query(query: flux) { response, error in
         if let error = error {
             callback(.failure(error))
             return
         }
         if let response = response {
             let chargeSessions = ...
             callback(.success(chargeSessions))
         }
    }
}
  1. Create an await/async wrapper for this function:
func getChargeList(from: Int, until: Int) async -> Result<[Charge], Error> {
    await withCheckedContinuation { continuation in
        runQuery(from: from, until: until) { result in
            continuation.resume(returning: result)
        }
    }
}

The use of Result is optional of course, but it allows to conveniently pack both successful and failed cases.

Also you could fit it into 1 function, but then you have 4 level of braces - not a convenient code to read.

  • Related