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.
Sponsor Hacking with Swift and reach the world's largest Swift community!
Link copied to your pasteboard.