Async await is part of Swift concurrency. If you are new to concurrency, you should read this article first: Concurrency In Swift.
async/await allows us to write asynchronous code in Swift. It enables us to define asynchronous functions and manage concurrency in a way that looks like synchronous code. So code becomes more readable and maintainable. The async keyword used to define a function and the await keyword used to call an asynchronous function.
Defining an Asynchronous Function
To define an asynchronous function, we need to use the async keyword:
func fetchData() async throws -> String {
// Simulate a network request
try await Task.sleep(nanoseconds: 2 * 1_000_000_000) // 2cseconds delay
let data = "Fetched Data"
return data
}
If a function returns anything, we need to use the throws keyword. This function will return a String.
Calling an Asynchronous Function
To call an asynchronous function, use the await keyword. This must be done from within an asynchronous context. Here is an example of how to call an async function in a regular Swift program:
import Foundation
func fetchData() async throws -> String {
// Simulate a network request
try await Task.sleep(nanoseconds: 5 * 1_000_000_000) // 5 second delay
let data = "Fetched Data"
return data
}
let data = try await fetchData()
print("Data received: \(data)")
If we run the program, we will see Data received: Fetched Data in the console after 5 seconds.
In SwiftUI, we can’t directly call an asynchronous function. We need to use the Task or .task view modifier to call an asynchronous function from the SwiftUI main thread. Here is an example, where we have simulated a network call:
import SwiftUI
struct ContentView: View {
@State private var data:String?
var body: some View {
VStack {
if let data {
Text("Data received: \(data)")
} else {
Text("Loading...")
}
}
.task {
do{
data = try await fetchData()
} catch{
print(error.localizedDescription)
}
}
}
func fetchData() async throws -> String {
// Simulate a network request
try await Task.sleep(nanoseconds: 2 * 1_000_000_000) // 2 seconds delay
let data = "Fetched Data"
return data
}
}
#Preview {
ContentView()
}
Here, the .task view modifier is used to run asynchronous code when a view appears.
.task Modifier: The .task view modifier is a convenient way to run asynchronous code when a view appears. It ensures that the provided asynchronous work is tied to the view’s lifecycle.
Here is another example of how you can define and call an asynchronous function in SwiftUI. This time we used the Task modifier:
import SwiftUI
struct ContentView: View {
@State private var data:String?
@State var isLoading = false
var body: some View {
VStack {
if let data {
Text("Data received: \(data)")
}
if isLoading{
ProgressView()
}
}
Button("Fetch Data"){
Task{
isLoading = true
do{
data = try await fetchData()
}
isLoading = false
}
}
}
func fetchData() async throws -> String {
// Simulate a network request
try await Task.sleep(nanoseconds: 2 * 1_000_000_000) // 2 seconds delay
let data = "Fetched Data"
return data
}
}
#Preview {
ContentView()
}
In this example, we showed a ProgressView() while fetching data, like a regular app. Now let’s fetch real data from remote API using async/await.
Task: The Task type is used to create and manage units of asynchronous work. It can be used to start an asynchronous operation in various contexts, such as in a function or an event handler.
Example of Fetching Data from a Remote API
Here’s a simple example demonstrating the use of async and await in a SwiftUI app. This example will fetch data from a remote API and display it in a view.
import SwiftUI
struct Post: Identifiable, Codable {
let id: Int
let title: String
let body: String
}
struct ContentView: View {
@State var posts: [Post] = []
var body: some View {
NavigationStack {
List(posts) { post in
VStack(alignment: .leading) {
Text(post.title)
.font(.headline)
Text(post.body)
.font(.subheadline)
.foregroundColor(.secondary)
}
}
.navigationTitle("Async Await Demo")
.navigationBarTitleDisplayMode(.inline)
.task {
await posts = fetchPosts()
}
}
}
func fetchPosts() async -> [Post]{
guard let url = URL(string: "https://jsonplaceholder.typicode.com/posts") else { return []}
do {
let (data, _) = try await URLSession.shared.data(from: url)
let fetchedPosts = try JSONDecoder().decode([Post].self, from: data)
return fetchedPosts
} catch {
print("Failed to fetch posts: \(error)")
return []
}
}
}
#Preview {
ContentView()
}
Here,
- An async function
fetchData()that is usedURLSessionto fetch data from a network request. - We used.
taskmodifier to call the async function from a non-async context. When the List view appears, it will call thefetchPosts()function.
Output:

When you run this example, the app will fetch a list of posts from the mock API (https://jsonplaceholder.typicode.com/posts) and display them in a list view. The task modifier ensures that the fetchPosts function is called when the view appears, and the posts are displayed once they are fetched.