How to use async/await in synchronous Swift code with tasks

Learn how to use tasks to call async/await methods from synchronous code.

Natascha Fadeeva
2 min readApr 6, 2022

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.

--

--