Problem
When processing collections in Swift, the map method executes transformations sequentially.
For intensive operations or those involving I/O—such as HTTP requests, file reading, or database queries—this can become a bottleneck.
The goal is to leverage concurrency to execute multiple transformations in parallel, ensuring safety with Sendable and error handling.
Solution
A Sequence extension is created that adds concurrentMap.
Internally, it uses withThrowingTaskGroup, which allows:
- Launching a task for each element in the sequence.
- Executing all transformations in parallel, respecting Swift’s structured concurrency model.
- Automatically propagating the first error that occurs, canceling the remaining tasks.
The use of @Sendable ensures that both elements and results are safe in concurrent environments.
This way, any asynchronous operation can benefit from parallel execution without sacrificing code clarity.
extension Sequence where Element: Sendable {
func concurrentMap<T: Sendable>(
_ transform: @escaping @Sendable (Element) async throws -> T
) async throws -> [T] {
try await withThrowingTaskGroup(of: T.self) { group in
for element in self {
group.addTask {
try await transform(element)
}
}
return try await group.reduce(into: []) {
$0.append($1)
}
}
}
}
Result
With concurrentMap, you achieve significant performance gains in asynchronous operations that can execute in parallel.
The pattern respects Swift’s structured concurrency rules, avoids data races, and maintains the same declarative style as map, making its adoption in existing projects straightforward.
Usage example:
let urls: [URL] = [...]
let contents = try await urls.concurrentMap {
try await fetchContent(from: $0)
}
This approach is ideal for batch HTTP requests, image processing, bulk data reading, or any scenario requiring maximum safe parallelism.
If you need guaranteed ordering or want to limit resource consumption to one task at a time, I covered that in Async Map. And if you want to combine both strategies — concurrent processing within controlled chunks — I built that in Async Concurrent Map.
Keep coding, keep running 🏃♂️