Async/Await in SwiftUI
Written by Team Kodeco
Async/await is a modern Swift feature introduced in Swift 5.5 that simplifies asynchronous programming, making your code cleaner and more intuitive. It is especially handy in SwiftUI where you can update your UI seamlessly after performing background tasks.
Example: Joke Fetcher App
In this example, you will build a SwiftUI app that fetches a random joke from the public JokeAPI whenever the user taps a button. You’ll use async/await to handle the networking and present the jokes in a friendly interface.
Step 1: Define the Model
Create a Joke
struct that conforms to Codable
to map the JSON response from the API:
struct Joke: Codable {
let setup: String
let delivery: String
}
Step 2: Create a Joke Fetching Function
Write an asynchronous function using async/await to fetch a random joke:
func fetchJoke() async throws -> Joke {
let url = URL(string: "https://v2.jokeapi.dev/joke/Programming?type=twopart")!
let request = URLRequest(url: url)
let (data, _) = try await URLSession.shared.data(for: request)
let joke = try JSONDecoder().decode(Joke.self, from: data)
return joke
}
Let’s break down this method:
-
async
Keyword: Declaring the function withasync
means that it can perform asynchronous operations. You can think of this as a task that can run in the background without blocking the rest of your code. -
await
Usage: Inside the function,await
is used before theURLSession.shared.data(for: request)
call. This is the point where the function will pause and wait for the data to be fetched from the network. Once the data is ready, the function will resume and continue executing the next lines. -
Error Handling: The function is marked with
throws
, meaning it can throw an error if something goes wrong (like a network failure). This must be handled by the caller of the function, as you’ll see below.
Step 3: Build the SwiftUI View
Now, you’ll build the SwiftUI view that displays the joke and includes a button to fetch a new one.
struct ContentView: View {
@State private var joke: Joke?
@State private var isLoading = false
var body: some View {
VStack(spacing: 8) {
if let joke = joke {
Text(joke.setup)
.font(.title)
Text(joke.delivery)
.font(.headline)
} else {
Text("Tap to fetch a joke!")
}
Button {
Task {
isLoading = true
do {
joke = try await fetchJoke()
} catch {
print("Failed to fetch joke: \(error)")
}
isLoading = false
}
} label: {
if isLoading {
ProgressView()
.progressViewStyle(.circular)
.padding(.horizontal)
} else {
Text("Fetch Joke")
}
}
.disabled(isLoading)
.buttonStyle(.borderedProminent)
.padding()
}
.multilineTextAlignment(.center)
.padding(.horizontal)
}
}
Tap the Fetch Joke button and your preview should look like:
Here’s what’s happening in the view:
-
Task Initialization: You use
Task
to run the asynchronous function. Inside the task, you’re free to use async/await as needed. -
await
Usage: You call your asynchronous functionfetchJoke
usingawait
. This means that the UI will wait for the joke to be fetched but without freezing the interface. It allows other interactions or animations to continue running smoothly. -
Error Handling: Inside the
do
block, you’re trying to call the asynchronous function. If it throws an error, thecatch
block will handle it, allowing for graceful error handling. -
State Updates: You use SwiftUI’s
@State
to keep track of the joke and the loading status. When the async function completes, it updates these state variables, causing the UI to refresh and display the new joke.
This simple app demonstrates how async/await in Swift can make asynchronous code more readable and intuitive, especially in the context of SwiftUI. By using modern Swift concurrency, developers can write more robust and maintainable code, making complex tasks like networking more straightforward and enjoyable to implement.