Home > Mobile >  Is it necessary to always use async in all functions when await is used in Swift
Is it necessary to always use async in all functions when await is used in Swift

Time:08-21

I have the following example in Swift:

func call1() { call2() }
func call2() { call3() }
func call3() { call4() }
func call4() { call5() }
func call5() { print("finish") }

If I want to make the call5() an async function, is it mandatory to change all other calls to async to make call5() to work properly?

CodePudding user response:

Calling an async function requires a matching await. To make it work in a sync context you need to wrap the call in a task.

Task { await call5() }

This will spawn a concurrent task on the current thread.

Here is the WWDC talk with more details about structured concurrency. https://developer.apple.com/videos/play/wwdc2021/10134/

CodePudding user response:

You don’t have to make them all async, but you might consider doing so. Specifically, by making them all asynchronous, you enjoy two benefits:

  • The caller of call1 can now know when the whole sequence is done, all the way down to call5.

  • If the caller of call1 gets canceled, structured concurrency will make sure that the cancelation is automatically propagated all the way down to call5.

So, you have two options:

  1. Leave call1-call4 synchronous, but have call4 invoke call5 with Task { … }. As described above, the caller of call1 loses the ability to know when call5 is done and you cannot enjoy automatic cancelation propagation:

    func call1() { call2() }
    func call2() { call3() }
    func call3() { call4() }
    func call4() { Task { await call5() } }    // opts out of structured concurrency
    func call5() async { … }
    
  2. You can make them all asynchronous, so that the caller of call1 can know when the whole sequence is done and enjoy automatic cancelation propagation.

    func call1() async { await call2() }
    func call2() async { await call3() }
    func call3() async { await call4() }
    func call4() async { await call5() }
    func call5() async { … }
    

In short, neither approach is necessarily right or wrong: It depends upon precisely what call5 is doing asynchronously.

Also, if refactoring a legacy code base, you don’t have to jump to option 2 immediately, but rather you can start with option 1, and slowly progress to option 2 over time. See WWDC 2021 video Swift concurrency: Update a sample app for examples of how you might do this migration over time.

  • Related