Concurrencia Accesible de Swift 6.2
Patrones para adoptar el modelo de concurrencia de Swift 6.2, donde el código se ejecuta en un solo hilo por defecto y la concurrencia se introduce explícitamente. Elimina errores comunes de carrera de datos sin sacrificar rendimiento.
Cuándo Activarlo
- Migrando proyectos de Swift 5.x o 6.0/6.1 a Swift 6.2
- Resolviendo errores del compilador sobre seguridad de carreras de datos
- Diseñando arquitectura de aplicaciones basada en MainActor
- Descargando trabajo intensivo de CPU a hilos de fondo
- Implementando conformancias de protocolos en tipos aislados en MainActor
- Habilitando ajustes de construcción de Concurrencia Accesible en Xcode 26
Problema Central: Descarga Implícita en Segundo Plano
En Swift 6.1 y versiones anteriores, las funciones async podían descargarse implícitamente a hilos de fondo, causando errores de carrera de datos incluso en código aparentemente seguro:
// Swift 6.1: ERROR
@MainActor
final class StickerModel {
let photoProcessor = PhotoProcessor()
func extractSticker(_ item: PhotosPickerItem) async throws -> Sticker? {
guard let data = try await item.loadTransferable(type: Data.self) else { return nil }
// Error: Enviar 'self.photoProcessor' corre el riesgo de causar carreras de datos
return await photoProcessor.extractSticker(data: data, with: item.itemIdentifier)
}
}
Swift 6.2 soluciona esto: las funciones async permanecen en el actor que las llama por defecto.
// Swift 6.2: OK — async permanece en MainActor, no hay carrera de datos
@MainActor
final class StickerModel {
let photoProcessor = PhotoProcessor()
func extractSticker(_ item: PhotosPickerItem) async throws -> Sticker? {
guard let data = try await item.loadTransferable(type: Data.self) else { return nil }
return await photoProcessor.extractSticker(data: data, with: item.itemIdentifier)
}
}
Patrón Central — Conformancias Aisladas
Los tipos MainActor ahora pueden conformarse a protocolos no aislados de forma segura:
protocol Exportable {
func export()
}
// Swift 6.1: ERROR — cruza a código aislado en el actor principal
// Swift 6.2: OK con conformancia aislada
extension StickerModel: @MainActor Exportable {
func export() {
photoProcessor.exportAsPNG()
}
}
El compilador asegura que la conformancia solo se use en el actor principal:
// OK — ImageExporter también es @MainActor
@MainActor
struct ImageExporter {
var items: [any Exportable]
mutating func add(_ item: StickerModel) {
items.append(item) // Seguro: mismo aislamiento de actor
}
}
// ERROR — el contexto no aislado no puede usar la conformancia de MainActor
nonisolated struct ImageExporter {
var items: [any Exportable]
mutating func add(_ item: StickerModel) {
items.append(item) // Error: No se puede usar la conformancia aislada del actor principal aquí
}
}
Patrón Central — Variables Globales y Estáticas
Protege el estado global/estático con MainActor:
// Swift 6.1: ERROR — el tipo no Sendable puede tener estado mutable compartido
final class StickerLibrary {
static let shared: StickerLibrary = .init() // Error
}
// Solución: Anotar con @MainActor
@MainActor
final class StickerLibrary {
static let shared: StickerLibrary = .init() // OK
}
Modo de Inferencia por Defecto de MainActor
Swift 6.2 introduce un modo donde MainActor se infiere por defecto — sin necesidad de anotaciones manuales:
// Con inferencia por defecto de MainActor habilitada:
final class StickerLibrary {
static let shared: StickerLibrary = .init() // Implícitamente @MainActor
}
final class StickerModel {
let photoProcessor: PhotoProcessor
var selection: [PhotosPickerItem] // Implícitamente @MainActor
}
extension StickerModel: Exportable { // Conformancia implícitamente @MainActor
func export() {
photoProcessor.exportAsPNG()
}
}
Este modo es optativo y se recomienda para aplicaciones, scripts y otros objetivos ejecutables.
Patrón Central — @concurrent para Trabajo en Segundo Plano
Cuando necesitas paralelismo real, descarga explícitamente con @concurrent:
Importante: Este ejemplo requiere los ajustes de construcción de Concurrencia Accesible — SE-0466 (aislamiento por defecto de MainActor) y SE-0461 (NonisolatedNonsendingByDefault). Con estos habilitados,
extractStickerpermanece en el actor del llamante, haciendo seguro el acceso a estado mutable. Sin estos ajustes, este código tiene una carrera de datos — el compilador lo marcará.
nonisolated final class PhotoProcessor {
private var cachedStickers: [String: Sticker] = [:]
func extractSticker(data: Data, with id: String) async -> Sticker {
if let sticker = cachedStickers[id] {
return sticker
}
let sticker = await Self.extractSubject(from: data)
cachedStickers[id] = sticker
return sticker
}
// Descarga trabajo costoso al grupo de hilos concurrentes
@concurrent
static func extractSubject(from data: Data) async -> Sticker { /* ... */ }
}
// Los llamantes deben usar await
let processor = PhotoProcessor()
processedPhotos[item.id] = await processor.extractSticker(data: data, with: item.id)
Para usar @concurrent:
- Marca el tipo contenedor como
nonisolated - Añade
@concurrenta la función - Añade
asyncsi no es asíncrona ya - Añade
awaiten los puntos de llamada
Decisiones Clave de Diseño
| Decisión | Justificación |
|---|---|
| Un solo hilo por defecto | El código más natural está libre de carreras de datos; la concurrencia es optativa |
| async permanece en el actor llamante | Elimina la descarga implícita que causaba errores de carrera de datos |
| Conformancias aisladas | Los tipos MainActor pueden conformarse a protocolos sin soluciones inseguras |
@concurrent optativo explícito | La ejecución en segundo plano es una decisión deliberada de rendimiento, no accidental |
| Inferencia por defecto de MainActor | Reduce las anotaciones @MainActor repetitivas para objetivos de aplicación |
| Adopción optativa | Ruta de migración no disruptiva — habilita las características de forma incremental |
Pasos de Migración
- Habilitar en Xcode: Compilador Swift > sección Concurrencia en Ajustes de Construcción
- Habilitar en SPM: Usa la API
SwiftSettingsen el manifiesto del paquete - Usar herramientas de migración: Cambios automáticos de código a través de swift.org/migration
- Empezar con valores por defecto de MainActor: Habilita el modo de inferencia para objetivos de aplicación
- Añadir
@concurrentdonde sea necesario: Primero perfile, luego descargue las rutas calientes - Probar exhaustivamente: Los problemas de carrera de datos se convierten en errores en tiempo de compilación
Mejores Prácticas
- Empieza en MainActor — escribe código de un solo hilo primero, optimiza después
- Usa
@concurrentsolo para trabajo intensivo de CPU — procesamiento de imágenes, compresión, cálculos complejos - Habilita el modo de inferencia de MainActor para objetivos de aplicación que son mayormente de un solo hilo
- Perfila antes de descargar — usa Instruments para encontrar cuellos de botella reales
- Protege los globales con MainActor — el estado mutable global/estático necesita aislamiento de actor
- Usa conformancias aisladas en lugar de soluciones
nonisolatedo envoltorios@Sendable - Migra de forma incremental — habilita las características una a la vez en los ajustes de construcción
Antipatrones a Evitar
- Aplicar
@concurrenta cada función async (la mayoría no necesita ejecución en segundo plano) - Usar
nonisolatedpara suprimir errores del compilador sin entender el aislamiento - Mantener patrones legacy de
DispatchQueuecuando los actores ofrecen la misma seguridad - Omitir comprobaciones de
model.availabilityen código de Modelos Fundacionales relacionado con concurrencia - Pelear contra el compilador — si reporta una carrera de datos, el código tiene un problema real de concurrencia
- Asumir que todo código async se ejecuta en segundo plano (por defecto en Swift 6.2: permanece en el actor llamante)
Cuándo Usarlo
- Todos los proyectos nuevos de Swift 6.2+ (la Concurrencia Accesible es la opción recomendada por defecto)
- Migrar aplicaciones existentes de Swift 5.x o 6.0/6.1
- Resolver errores del compilador sobre seguridad de carreras de datos durante la adopción de Xcode 26
- Construir arquitecturas de aplicaciones centradas en MainActor (la mayoría de aplicaciones de UI)
- Optimización de rendimiento — descargar cómputos pesados específicos al segundo plano