Parallelism in Swift Concurrency with async let
Swift's concurrency model provides a powerful and elegant solution to Parallelism: the async let binding.

Search for a command to run...
Swift's concurrency model provides a powerful and elegant solution to Parallelism: the async let binding.

No comments yet. Be the first to comment.
One of the most effective ways to improve the user experience of an iOS app is by utilizing features that feel native and familiar to the user.

https://youtu.be/L0X1BK4D6mc Handling Optionals is a bread-and-butter task for Swift developers. We all know the standard way to unwrap a value or provide a fallback, but things get tricky when we are dealing with String interpolation and data type...

The fundamental benefit of these components is that they automatically handle the user interaction required for resizing

https://www.youtube.com/watch?v=CiObtyFFuSs The video is in Portuguese but with English subtitles This article explores the critical distinction between the .task and .onAppear view modifiers in SwiftUI, a common point of confusion and a frequent...

awaitIn Swift's structured concurrency, the await keyword is used to pause the execution of the current task until the asynchronous function it calls returns a result. While this is essential for managing dependencies, using multiple sequential await calls for tasks that do not depend on each other forces the program to wait for each operation to complete before starting the next.
Consider a scenario where an application needs to fetch six independent pieces of data (simulated here by six network requests). The sequential approach looks like this:
Task { let startInstant = clock.now
// Each 'await' pauses execution until the previous one is complete
let joke1 = try! await requestJoke(index: 1)
let joke2 = try! await requestJoke(index: 2)
let joke3 = try! await requestJoke(index: 3)
let joke4 = try! await requestJoke(index: 4)
let joke5 = try! await requestJoke(index: 5)
let joke6 = try! await requestJoke(index: 6)
jokes = [joke1, joke2, joke3, joke4, joke5, joke6]
print("Time taken = \(clock.now - startInstant)")
}
private func requestJoke(index: Int) async throws -> String {
let url = URL(string: "https://api.chucknorris.io/jokes/random" )!
let (data, _) = try await URLSession.shared.data(from: url)
let decoded = try JSONSerialization.jsonObject(with: data, options: []) print("(index)")
if let dict = decoded as? [String: Any] {
return dict["value"] as! String
}
return "Error"
}
In a test environment, this sequential execution took approximately 2.2 to 2.3 seconds to complete all six requests. The total time is roughly the sum of the individual request times, as they run one after the other.
The async let binding is designed to solve this exact problem. It allows you to declare an asynchronous value, immediately starting the task in parallel with the current execution flow. The task runs concurrently in the background, and the current task continues without pausing. You only use await later when you actually need the result of that task.
By using async let, all six network requests are initiated almost simultaneously, and the program only pauses at the final step to collect the results.
Task { let startInstant = clock.now
// Each 'await' pauses execution until the previous one is complete
async let joke1 = try! requestJoke(index: 1)
async let joke2 = try! requestJoke(index: 2)
async let joke3 = try! requestJoke(index: 3)
async let joke4 = try! requestJoke(index: 4)
async let joke5 = try! requestJoke(index: 5)
async let joke6 = try! requestJoke(index: 6)
// The 'await' here waits for all six concurrent tasks to finish
jokes = await [try! joke1, try! joke2, try! joke3, try! joke4, try! joke5, try! joke6]
print("Time taken = \(clock.now - startInstant)")
}
The performance improvement is dramatic. In the same test environment, the parallel execution using async let completed in approximately 1.0 to 1.1 seconds. This is a reduction of over 50% in execution time, as the total time is now dictated by the longest-running task, not the sum of all tasks.
The following table summarizes the performance difference between the two approaches:
| Execution Method | Task Initiation | Total Execution Time (Approx.) | Performance Gain |
| Sequential await | One task starts after the previous one finishes. | 2.2 - 2.3 seconds | Baseline |
| Parallel async let | All tasks start concurrently. | 1.0 - 1.1 seconds | ~55% Faster |
For any Swift application that needs to perform multiple independent asynchronous operations, adopting async let is a straightforward and highly effective way to leverage the power of structured concurrency and achieve significant performance gains. It provides a clean, readable, and modern alternative to older techniques like DispatchGroup, ensuring your application remains responsive and efficient.
By replacing sequential await calls with async let for independent tasks, you can ensure that your application is utilizing system resources to their fullest, leading to a faster and more fluid user experience.