Problema
Cuando procesamos colecciones en Swift, el método map ejecuta las transformaciones de forma secuencial.
En operaciones intensivas o que implican I/O —como peticiones HTTP, lectura de archivos o consultas de base de datos— esto puede ser un cuello de botella.
El objetivo es aprovechar la concurrencia para ejecutar múltiples transformaciones en paralelo, garantizando seguridad con Sendable y manejo de errores.
Solución
Se crea una extensión de Sequence que añade concurrentMap.
Internamente usa withThrowingTaskGroup, lo que permite:
- Lanzar una tarea por cada elemento de la secuencia.
- Ejecutar todas las transformaciones en paralelo, respetando el modelo de concurrencia estructurada de Swift.
- Propagar automáticamente el primer error que se produzca, cancelando las tareas restantes.
El uso de @Sendable asegura que tanto los elementos como el resultado sean seguros en entornos concurrentes.
De esta manera, cualquier operación asíncrona puede beneficiarse de la ejecución en paralelo sin sacrificar la claridad del código.
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)
}
}
}
}
Resultado
Con concurrentMap se consigue una ganancia de rendimiento significativa en operaciones asíncronas que pueden ejecutarse en paralelo.
El patrón respeta las reglas de concurrencia estructurada de Swift, evita data races y mantiene el mismo estilo declarativo que map, por lo que su adopción en proyectos existentes es sencilla.
Ejemplo de uso:
let urls: [URL] = [...]
let contents = try await urls.concurrentMap {
try await fetchContent(from: $0)
}
Este enfoque es ideal para peticiones HTTP en lotes, procesamiento de imágenes, lectura masiva de datos o cualquier escenario que requiera máximo paralelismo seguro.
Si necesitas orden garantizado o limitar el consumo de recursos a una tarea a la vez, lo cubrí en Async Map. Y si quieres combinar ambas estrategias — procesamiento concurrente dentro de chunks controlados — lo construí en Async Concurrent Map.
Keep coding, keep running 🏃♂️