How to use async/await in synchronous Swift code with tasks
Learn how to use tasks to call async/await methods from synchronous code.
The async/await feature introduced in Swift 5.5 allows us to write asynchronous code in a shorter and safer way. For a recap on async/await, check out this quick guide on async/await in Swift.
In some situations, we need to call async/await methods or properties from synchronous context. How can we do that?

Let’s look at an example. The following method uses the UNUserNotificationCenter’s async/await methods to ask for authorization to send notifications, then fetches the settings and returns them.
func requestNotificationAuthorization() async throws -> UNNotificationSettings { let notificationCenter = UNUserNotificationCenter.current() try await notificationCenter.requestAuthorization(options: [.alert, .sound]) let settings = await notificationCenter.notificationSettings() return settings}
We would like to call this method from our AppDelegate’s method applicationDidFinishLaunching(_ application: UIApplication).
Let’s try:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool { let settings = try await requestNotificationAuthorization() return true}
We get the compiler error async call in a function that does not support concurrency. Since the delegate method applicationDidFinishLaunching is not marked with async, we cannot just await a method.
Xcode suggests to add async to the function to resolve the error, but since it’s not our function, we cannot just change it.
That’s where tasks come in. A task is represented by the Task struct in Swift. When using a Task, we provide a closure that can contain synchronous or asynchronous code to perform.
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool { Task { do { let settings = try await requestNotificationAuthorization() // Handle settings } catch { // Handle error } } return true}
We don’t need to manually start the task, it starts running immediately after creation. We also don’t need to keep a reference to it — only if we need more control over the task like the possibility to cancel.
And that’s basically it. Tasks provide us with an easy way to bridge async/await with other Swift code.
Originally published at https://tanaschita.com.