Concurrencia Aproximable 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 los errores comunes de carrera de datos sin sacrificar el rendimiento.
Cuándo activarlo
- Migrar proyectos de Swift 5.x o 6.0/6.1 a Swift 6.2
- Resolver errores del compilador sobre seguridad contra carreras de datos
- Diseñar arquitecturas de aplicaciones basadas en MainActor
- Descargar trabajo intensivo de CPU a hilos de fondo
- Implementar conformidades de protocolo en tipos aislados por MainActor
- Habilitar los ajustes de compilación de Concurrencia Aproximable en Xcode 26
Problema central: descarga implícita a segundo plano
En Swift 6.1 y versiones anteriores, las funciones asíncronas 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 asíncronas permanecen en el actor que las llama por defecto.
// Swift 6.2: OK — async permanece en MainActor, sin 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: conformidades aisladas
Los tipos de MainActor ahora pueden cumplir con protocolos no aislados de forma segura:
protocol Exportable {
func export()
}
// Swift 6.1: ERROR — cruza al código aislado por el actor principal
// Swift 6.2: OK con conformidad aislada
extension StickerModel: @MainActor Exportable {
func export() {
photoProcessor.exportAsPNG()
}
}
El compilador asegura que la conformidad 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 — un contexto no aislado no puede usar la conformidad de MainActor
nonisolated struct ImageExporter {
var items: [any Exportable]
mutating func add(_ item: StickerModel) {
items.append(item) // Error: la conformidad aislada por el actor principal no se puede usar 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 predeterminada de MainActor
Swift 6.2 introduce un modo en el que MainActor se infiere por defecto, sin necesidad de anotaciones manuales:
// Con la inferencia predeterminada 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 { // Conformidad implícitamente @MainActor
func export() {
photoProcessor.exportAsPNG()
}
}
Este modo es opcional 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 compilación de Concurrencia Aproximable: SE-0466 (aislamiento predeterminado de MainActor) y SE-0461 (NonisolatedNonsendingByDefault). Con estos habilitados,
extractStickerpermanece en el actor de quien llama, haciendo seguro el acceso al estado mutable. Sin estos ajustes, este código tiene una carrera de datos: el compilador la 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 { /* ... */ }
}
// Quienes llaman 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 - Agrega
@concurrenta la función - Agrega
asyncsi aún no es asíncrona - Agrega
awaiten los sitios 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 opcional |
| Async permanece en el actor que llama | Elimina la descarga implícita que causaba errores de carrera de datos |
| Conformidades aisladas | Los tipos de MainActor pueden cumplir con protocolos sin soluciones inseguras |
@concurrent explícito y optativo | La ejecución en segundo plano es una decisión de rendimiento deliberada, no accidental |
| Inferencia predeterminada de MainActor | Reduce la necesidad de anotaciones @MainActor 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 de Concurrencia en los ajustes de compilación
- Habilitar en SPM: Usa la API
SwiftSettingsen el manifiesto del paquete - Usar las herramientas de migración: Cambios automáticos de código vía swift.org/migration
- Empezar con valores predeterminados de MainActor: Habilita el modo de inferencia para objetivos de aplicación
- Agregar
@concurrentdonde sea necesario: Perfila primero, luego descarga las rutas calientes - Probar a fondo: 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 principalmente 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 del actor
- Usa conformidades aisladas en lugar de soluciones
nonisolatedo envoltorios@Sendable - Migra de forma incremental — habilita las características una a una en los ajustes de compilación
Antipatrones a evitar
- Aplicar
@concurrenta cada función asíncrona (la mayoría no necesita ejecución en segundo plano) - Usar
nonisolatedpara suprimir errores del compilador sin entender el aislamiento - Mantener patrones legacy
DispatchQueuecuando los actores proporcionan la misma seguridad - Omitir las comprobaciones de
model.availabilityen código de Foundation Models relacionado con concurrencia - Pelear contra el compilador — si informa de una carrera de datos, el código tiene un problema de concurrencia real
- Asumir que todo el código asíncrono se ejecuta en segundo plano (Swift 6.2 por defecto: permanece en el actor que llama)
Cuándo usarlo
- Todos los proyectos nuevos con Swift 6.2+ (Concurrencia Aproximable es la opción predeterminada recomendada)
- Migrar aplicaciones existentes desde Swift 5.x o 6.0/6.1 con concurrencia
- Resolver errores del compilador sobre seguridad contra carreras de datos durante la adopción de Xcode 26
- Construir arquitecturas de aplicaciones centradas en MainActor (la mayoría de las aplicaciones de interfaz de usuario)
- Optimización de rendimiento: descargar cálculos pesados específicos a segundo plano