async/await
Let's delve into the fascinating world of Swift's async/await
.
This powerful feature, introduced in Swift 5.5, revolutionizes asynchronous programming, making it more intuitive and readable. Buckle up as we explore the ins and outs of async/await
!
What is async/await
?
At its core, async/await
simplifies handling asynchronous tasks. It allows you to write asynchronous code that looks almost synchronous. Here's how it works:
-
async
Functions: You mark a function asasync
. Inside this function, you can useawait
to pause execution until an asynchronous operation completes. -
await
Expression: When you encounter anawait
expression, the function suspends execution until the awaited task finishes. Meanwhile, other tasks can continue running concurrently.
Example 1: Fetching Data
Let's start with a common scenario: fetching data from a remote API. Imagine we have a callback-based function for fetching data:
func fetchData(completion: @escaping (Data?, Error?) -> Void) {
let url = URL(string: "https://api.example.com/data")!
URLSession.shared.dataTask(with: url) { data, _, error in
completion(data, error)
}.resume()
}
Now, let's convert this to an async
function using async/await
:
func fetchData() async throws -> Data {
let url = URL(string: "https://api.example.com/data")!
return try await URLSession.shared.data(from: url).0
}
In the async
version:
- We use
try await
to wait for the data to be fetched. - The error handling is cleaner, thanks to Swift's
throws
.
Example 2: Multiple API Calls
Imagine we have a callback-based function for fetching data:
func fetchUser(id: Int, completion: @escaping (User?, Error?) -> Void) {
// Fetch user details...
}
func fetchPosts(for user: User, completion: @escaping ([Post]?, Error?) -> Void) {
// Fetch posts for the user...
}
// Usage:
fetchUser(id: 1) { user, error in
guard let user = user else {
print(error)
}
// Guard for no error
self.fetchPosts(for: user) { posts, error in
// Process user and posts
}
}
Suppose we need to fetch a user and then fetch their posts. In the callback-based world, this can get messy. But with async/await
, it's elegant:
func fetchUser(id: Int) async throws -> User {
// Fetch user details...
}
func fetchPosts(for user: User) async throws -> [Post] {
// Fetch posts for the user...
}
// Usage
Task {
do {
let user = try await fetchUser(id: 1)
let posts = try await fetchPosts(for: user)
// Process user and posts
} catch {
// Handle errors
}
}
Running multiple tasks concurrently
func fetchUser(id: Int) async -> User {
// Fetch user details...
}
func fetchPosts(for userID: Int) async -> [Post] {
// Fetch posts for the user...
}
// Usage
import Foundation
Task {
async let user = fetchUser(id: 1)
async let userPosts = fetchPosts(for: 1)
await updateUI(user: user, userPosts: userPosts)
}
Wait/Sleep in a Task
Task {
// Delay the task by 1 second:
try await Task.sleep(nanoseconds: 1_000_000_000)
// Perform our operation
// ...
}
Cancellation
func fetchUser(id: Int) async -> User {
// Fetch user details...
}
func fetchPosts(for userID: Int) async -> [Post] {
// Fetch posts for the user...
}
// Usage
import Foundation
let task = Task {
async let user = fetchUser(id: 1)
async let userPosts = fetchPosts(for: 1)
await updateUI(user: user, userPosts: userPosts)
}
// Cancel the task
task.cancel()
Difference between Task
and Task.detached
Task
is a structured concurrency task that runs within the scope of the current task.
It is automatically cancelled when the parent task is cancelled.
Task.detached
is a detached task that runs independently of the current task.
It is not automatically cancelled when the parent task is cancelled.
Ensure that your code is (not)running on the main thread
Explicitly running on the main thread
Task { @MainActor in
}
Doing heavy work (background) and then updating the UI (main thread/@MainActor)
Task.detached(priority: .userInitiated) {
await someHeavyBackgroundOperation()
await MainActor.run {
// Perform UI updates
}
}
Doing heavy work on the background using nonisolated
and async
.
private nonisolated func doHeavyWork() async -> Bool {
sleep(10)
return true
}
Task {
let result = await doHeavyWork()
}
Limitations
async/await
is available starting from Swift 5.5.async/await
is not available on all platforms. It is available on iOS 15, macOS 12, watchOS 8, and tvOS 15.async/await
is not available in Objective-C code.
Conclusion
By embracing async/await
, you create a better development experience and write more efficient code. Say goodbye to callback hell and welcome a cleaner, more expressive way of handling asynchronous tasks in Swift. 🚀
Resources:
https://docs.swift.org/swift-book/LanguageGuide/Concurrency.html#async-await
https://developer.apple.com/documentation/xcode/improving-app-responsiveness
Read more
- ContentUnavailableView • 5 minutes reading time.
- Observable Geocoder • 3 minutes reading time.
- SwiftLeeds 2024, Day 1 • 11 minutes reading time.
Share
Share Mastodon Twitter LinkedIn Facebook