Asynchronous programming has become a cornerstone of modern app development. In the iOS ecosystem, Apple introduced native support for asynchronous programming with Swift, including the powerful async/await pattern. In this article, we'll delve into the usage of async/await in iOS development
Async/await is a programming paradigm that streamlines the execution of asynchronous code, making it appear more synchronous and readable. It eliminates the complexities of managing callbacks or completion handlers, allowing developers to write asynchronous code that same as synchronous code. With Swift's async/await, developers can efficiently handle tasks such as network requests, database queries, or file I/O without blocking the main thread
Define an async Function: To create an asynchronous function, simply use the async
keyword in the function declaration. This signifies that the function will execute asynchronous tasks.
func fetchData() async -> Data {
// async operations
}
Use await Inside an async Function: Within an async function, utilize the await
keyword to call other asynchronous functions or tasks. It suspends the current task until the awaited task is completed.
func fetchImageData() async -> Data {
let url = URL(string: "https://example.com/image.jpg")!
do {
let (data, _) = try await URLSession.shared.data(from: url)
return data
} catch {
// Handle errors
}
}
Mark the Caller as async: When calling an async function from another function, mark the calling function as async as well. This facilitates the use of await
for calling async functions.
func displayImage() async {
let imageData = await fetchImageData()
let image = UIImage(data: imageData)
// Update UI with the image
}
Error Handling: Error handling with async/await is straightforward. Utilize try
and catch
blocks to handle errors thrown within async functions.
func fetchImageDataWithThrowing(stringURL: URL) async throws -> Data {
guard let url = URL(string: stringURL)
else { throw NSError(domain: "Bad URL", code: 0) }
let (data, _) = try await URLSession.shared.data(from: url)
return data
}
func displayData() async {
let url = "https://example.com/image.jpg"
do {
let data = try await fetchImageDataWithThrowing(stringURL: url)
// Process data
} catch {
// Handle errors
}
}
Running async Code on the Main Thread: To update the UI on the main thread within async code, use await
to dispatch code to the main queue.
Task {
await MainActor.run {
// Update UI
}
}
or
Task { @MainActor in
// Update UI
}
Calling an Asynchronous Function in Synchronous Code: Utilize the Task
structure to denote the scope of an asynchronous call within synchronous code.
func upadeImg() {
Task {
let image = await fetchImageData()
MainActor.run {
// Update UI
}
}
}
Converting a Function with a Callback to Asynchronous: To create an asynchronous function, simply use the construct like withCheckedContinuation
or withCheckedThrowingContinuation
. This may help you if you want to use async/await at the network layer with iOS before version 15 and after 13.
extension URLSession {
func asyncDataRequest(url: URL) async throws -> Data {
try await withCheckedThrowingContinuation { next in
dataTask(with: url, completionHandler: { data, response, error in
if let error {
next.resume(throwing: error)
} else if let data {
next.resume(returning: data)
} else {
next.resume(throwing: NSError(domain: "Empty data", code: 0))
}
}).resume()
}
}
}
Running Multiple Queries in Parallel: Use constructs like async let
or withTaskGroup
or withThrowingTaskGroup
to execute multiple asynchronous methods concurrently, reducing lines of code and improving efficiency.
func fetchUser() async -> User {
async let imageData = await fetchImageData()
async let fullName = await fetchUserFullName()
return await User(avatar: imageData, fullName: fullName)
}
or withTaskGroup
func fetchImages(imageID: [String]) async -> [Data] {
await withTaskGroup(of: Data.self) { group in
imageID.forEach { id in
group.addTask {
await fetchImageData(byID: id)
}
}
return await group.reduce(into: [Data]()) { tmpResult, imageData in
tmpResult.append(imageData)
}
}
}
Async/await in Swift presents a powerful and readable approach to asynchronous programming in iOS applications. By following the outlined steps, developers can simplify their asynchronous code, improve app responsiveness, and leverage Swift's robust asynchronous programming capabilities to create more efficient and responsive iOS apps