Use Swift Concurrency with Completion Handlers
This post is brought to you by Emerge Tools, the best way to build on mobile.
let concatenatedThoughts = """
Welcome to Snips! Here, you'll find a short, fully code complete sample with two parts. The first is the entire code sample which you can copy and paste right into Xcode. The second is a step by step explanation. Enjoy!
"""
The Scenario
Convert an existing function that uses a completion handler over to Swift Concurrency.
struct CatFact: Codable {
let fact: String
}
// 1
func getCatFact(completion:@escaping ((String) -> Void)) {
let reqURL = URL(string: "https://catfact.ninja/fact")!
URLSession.shared.dataTask(with: URLRequest(url: reqURL)) { data, response, error in
guard let factData = data,
let catFact = try? JSONDecoder().decode(CatFact.self, from: factData) else {
completion("No cat facts today.")
return
}
completion(catFact.fact)
}.resume()
}
// 2
func getCatFact() async -> String {
// 3
await withCheckedContinuation { continuation in
getCatFact { fact in
continuation.resume(returning: fact)
}
}
}
With that, you can call getCatFact
using Swift Concurrency:
let result = await getCatFact()
Instead of this:
getCatFact { result in
print(result)
}
The Breakdown
Step 1
The first step is to realize that you don’t remove your existing function that uses a completion handler. Instead, you need to simply note what return type (if any) the completion handler returns. In this case, it’s just a String
.
Step 2
Create a new function that matches the original’s signature, and have its return type match whatever the completion handler returns. Again, that’s a String
here. Be sure to add async
, and throws
if it has error handling.
Step 3
Finally, call the original function within your new async version, and wrap it in a continuation. Within the completion handler, resume it and you’re done! If you’re using error handling, there is a throwing version of a continuation available, too.
Until next time ✌️