Suppose I have the following function:
func doWork(_ someValue: Int, completionHandler: () -> Void) {
let q = DispatchQueue()
q.async {
// Long time of work
completionHandler()
}
}
How do I turn it into async function so that I can call it using await doWork()
? Are there guidelines/principles/practices for this purpose?
I assume you cannot modify the API entirely? If you have full control, you can simply do something like:
func doWork(_ someValue) async {
// Long time of work
}
If you still want to maintain the completion handler API, but wrap it with an async version, you should take a look at Continuations. There's an article on hackingwithswift.com that might help: https://www.hackingwithswift.com/quick-start/concurrency/how-to-use-continuations-to-convert-completion-handlers-into-async-functions.
Using this approach, your existing function could be wrapped by a new async version quite easily.
func doWork(_ someValue: Int, completionHandler: @escaping () -> Void) {
let q = DispatchQueue(label: "MyLabel")
q.async {
// Long time of work
completionHandler()
}
}
func doWork(_ someValue: Int) async {
await withCheckedContinuation { continuation in
// Call the existing completion handler API.
doWork(someValue) {
// Resume the continuation to exit the async function.
continuation.resume()
}
}
}
Care should be taken to ensure you always call the completion handler in the original API, otherwise you could have a situation where your continuation doesn't resume. You know you've usually done this if you see something like this in the console: SWIFT TASK CONTINUATION MISUSE: doWork(_:) leaked its continuation!
.
Continuations can return values, and even throw errors if needed.
If you're curious about other functions, you can often use Xcode to refactor the function to be asynchronous, or generate an async wrapper (although mileage may vary depending on the function complexity). You can do this by selecting the function name, right clicking and choosing "Refactor", and then picking either "Convert Function to Async" or "Add Async Wrapper". The code generated from "Add Async Wrapper" is as follows, which isn't far from the example above:
@available(*, renamed: "doWork(_:)")
func doWork(_ someValue: Int, completionHandler: @escaping () -> Void) {
let q = DispatchQueue(label: "MyLabel")
q.async {
// Long time of work
completionHandler()
}
}
func doWork(_ someValue: Int) async {
return await withCheckedContinuation { continuation in
doWork(someValue) {
continuation.resume(returning: ())
}
}
}
Hope that helps.
-Matt