UPGRADE YOUR SKILLS: Learn advanced Swift and SwiftUI on Hacking with Swift+! >>

What calls the first async function?

Paul Hudson    @twostraws   

Updated for Xcode 15

You can only call async functions from other async functions, because they might need to suspend themselves and everything that is waiting for them. This leads to a bit of a chicken and egg problem: if only async functions can call other async functions, what starts it all – what calls the very first async function?

Well, there are three main approaches you’ll find yourself using.

First, in simple command-line programs using the @main attribute, you can declare your main() method to be async. This means your program will immediately launch into an async function, so you can call other async functions freely.

Here’s how that looks in code:

func processWeather() async {
    // Do async work here
}

@main
struct MainApp {
    static func main() async {
        await processWeather()
    }
}

Download this as an Xcode project

Second, in apps built with something like SwiftUI the framework itself has various places that can trigger an async function. For example, the refreshable() and task() modifiers can both call async functions freely.

Using the task() modifier we could write a simple “View Source” app that fetches the content of a website when our view appears:

struct ContentView: View {
    @State private var sourceCode = ""

    var body: some View {
        ScrollView {
            Text(sourceCode)
        }
        .task {
            await fetchSource()
        }
    }

    func fetchSource() async {
        do {
            let url = URL(string: "https://apple.com")!

            let (data, _) = try await URLSession.shared.data(from: url)
            sourceCode = String(decoding: data, as: UTF8.self).trimmingCharacters(in: .whitespacesAndNewlines)
        } catch {
            sourceCode = "Failed to fetch apple.com"
        }
    }
}

Download this as an Xcode project

Tip: Using task() will almost certainly run our code away from the main thread, but the @State property wrapper has specifically been written to allow us to modify its value on any thread.

The third option is that Swift provides a dedicated Task API that lets us call async functions from a synchronous function. Now, you might think “wait a minute – how can a synchronous function call an asynchronous function?” Well, it can’t – at least not directly. Remember, async functions might need to suspend themselves in the future, and synchronous functions don’t know how to do that.

When you use something like Task you’re asking Swift to run some async code. If you don’t care about the result you have nothing to wait for – the task will start running immediately while your own function continues, and it will always run to completion even if you don’t store the active task somewhere. This means you’re not awaiting the result of the task, so you won’t run the risk of being suspended. Of course, when you actually want to use any returned value from your task, that’s when await is required.

We’ll be looking at Swift’s Task API in detail later on, but for now we could quickly upgrade our little website source code viewer to work with any URL. This time we’re going to trigger the network fetch using a button press, which is not asynchronous by default, so we’re going to wrap our work in a Task. This is possible because we don’t need to wait for the task to complete – it will always run to completion as soon as it is made, and will take care of updating the UI for us.

Here’s how that looks:

struct ContentView: View {
    @State private var site = "https://"
    @State private var sourceCode = ""

    var body: some View {
        VStack {
            HStack {
                TextField("Website address", text: $site)
                    .textFieldStyle(.roundedBorder)
                Button("Go") {
                    Task {
                        await fetchSource()
                    }
                }
            }
            .padding()

            ScrollView {
                Text(sourceCode)
            }
        }
    }

    func fetchSource() async {
        do {
            let url = URL(string: site)!
            let (data, _) = try await URLSession.shared.data(from: url)
            sourceCode = String(decoding: data, as: UTF8.self).trimmingCharacters(in: .whitespacesAndNewlines)
        } catch {
            sourceCode = "Failed to fetch \(site)"
        }
    }
}

Download this as an Xcode project

BUILD THE ULTIMATE PORTFOLIO APP Most Swift tutorials help you solve one specific problem, but in my Ultimate Portfolio App series I show you how to get all the best practices into a single app: architecture, testing, performance, accessibility, localization, project organization, and so much more, all while building a SwiftUI app that works on iOS, macOS and watchOS.

Get it on Hacking with Swift+

Sponsor Hacking with Swift and reach the world's largest Swift community!

Similar solutions…

BUY OUR BOOKS
Buy Pro Swift Buy Pro SwiftUI Buy Swift Design Patterns Buy Testing Swift Buy Hacking with iOS Buy Swift Coding Challenges Buy Swift on Sundays Volume One Buy Server-Side Swift Buy Advanced iOS Volume One Buy Advanced iOS Volume Two Buy Advanced iOS Volume Three Buy Hacking with watchOS Buy Hacking with tvOS Buy Hacking with macOS Buy Dive Into SpriteKit Buy Swift in Sixty Seconds Buy Objective-C for Swift Developers Buy Beyond Code

Was this page useful? Let us know!

Average rating: 3.9/5

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.