What calls the first async function?
What calls the first async function? êŽë š
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()
}
}
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"
}
}
}
Tips
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)"
}
}
}