wandres.dev
CONCURRENCIA · async/await y actores

Actores, @MainActor y Sendable

Protege datos compartidos de las condiciones de carrera con actores, mantén la UI en el hilo principal con @MainActor, y entiende Sendable, la marca de lo seguro.

⏱ 14 min

Cuando varias tareas corren a la vez y tocan los mismos datos, aparecen las condiciones de carrera: bugs no deterministas, casi imposibles de reproducir. Los actores son la respuesta de Swift: protegen su estado para que solo se acceda de forma ordenada.

🎯 Al terminar esta lección sabrás
  • Qué es un actor y qué problema resuelve.
  • Aislamiento: por qué accedes a un actor con await.
  • @MainActor para el trabajo de interfaz.
  • Sendable: qué puede cruzar entre tareas con seguridad.

El problema: condiciones de carrera

Dos tareas incrementando el mismo contador a la vez pueden pisarse y perder cuentas. Un actor garantiza que solo una tarea toca su estado interno cada vez:

actor Contador {
    private var valor = 0
    func incrementar() { valor += 1 }
    func total() -> Int { valor }
}

Aislamiento: el await al acceder

El estado de un actor está aislado: desde fuera, accedes con await, porque puede que tengas que esperar tu turno. Swift lo impone en compilación:

let contador = Contador()
await contador.incrementar()      // desde fuera, siempre con await
let n = await contador.total()
Seguridad de datos garantizada por el compilador

La clave: el compilador no te deja acceder al estado de un actor sin pasar por su cola ordenada. No es una convención que puedas olvidar — es una regla del sistema de tipos. Las condiciones de carrera, esa categoría de bugs que aterroriza a los desarrolladores porque aparecen una vez de cada mil y nunca en el depurador, se vuelven imposibles por construcción. Escribes concurrencia agresiva con la tranquilidad de que Swift te avisa en compilación si algo no es seguro.

@MainActor: la interfaz vive en el hilo principal

Todo lo que toca la UI debe ejecutarse en el hilo principal. @MainActor marca código que siempre corre ahí:

@MainActor
class VistaModelo {
    var titulo = ""          // seguro tocarlo desde la UI
    func actualizar() { titulo = "Listo" }
}
ℹ️
Swift 6.2+: concurrencia amigable

Novedad importante de las versiones recientes: por defecto, tu código de app corre en el hilo principal (aislado a @MainActor) salvo que elijas explícitamente lanzar concurrencia. Esto invierte la carga: ya no tienes que anotar @MainActor por todas partes; es el punto de partida. Solo cuando mandas trabajo pesado a segundo plano entras en territorio concurrente. El resultado es que empezar es mucho más sencillo, y la concurrencia estricta deja de ser una barrera para principiantes.

Sendable: lo que puede cruzar con seguridad

Cuando pasas un valor de una tarea a otra, Swift necesita saber que es seguro compartirlo. Los tipos que lo son conforman a Sendable. Los tipos de valor (structs de datos simples) suelen serlo automáticamente:

struct Usuario: Sendable {    // seguro de pasar entre tareas
    let nombre: String
    let edad: Int
}

Las structs con solo propiedades Sendable lo son solas. Las clases mutables no lo son (por eso preferimos structs, otra vez el Nivel 1). Si necesitas compartir una clase entre tareas, protégela con un actor o hazla inmutable.

💡
Sendable es la pieza que conecta todo

Sendable es cómo el compilador rastrea qué es seguro mover entre hilos. Si ves un error de “no conforma a Sendable”, te está diciendo “esto no es seguro compartir así”. La solución suele ser: hazlo un tipo de valor inmutable, o envuélvelo en un actor. Entender Sendable es entender el modelo de seguridad completo de la concurrencia de Swift.

⚔️ Concurrencia segura
  1. Crea un actor con un contador privado y métodos para incrementarlo y leerlo.
  2. Accede a él desde fuera con await y observa que el compilador lo exige.
  3. Marca una clase de modelo de vista con @MainActor.
  4. Define una struct Sendable y razona por qué es segura para compartir entre tareas.