[SC] Sendable
Source: Dev.to
Preguntas
¿Por qué el compilador necesita saber si un valor es thread‑safe?
El compilador debe asegurarse de que un valor pueda trasladarse entre dominios de aislamiento sin provocar carreras de datos. Si el valor no es seguro, el compilador emitirá advertencias o errores para evitar comportamientos no determinísticos.
¿Qué es un isolation domain y para qué sirve?
Un dominio de aislamiento define una frontera dentro de la cual se puede acceder a un valor o referencia sin riesgo de carreras de datos. Existen tres tipos:
| Tipo | Descripción |
|---|---|
nonisolated | Dominio por defecto; no impone restricciones de concurrencia. |
actor | Cada instancia de actor tiene su propio dominio. |
| global actor | Un dominio compartido por varios tipos, propiedades y funciones (p. ej. @MainActor). |
¿Qué restricciones tiene el código nonisolated respecto al estado de otros dominios?
nonisolatedes el dominio de aislamiento por defecto y no tiene restricciones de concurrencia internas.- Puede modificar sin problema el estado de otro código del mismo tipo.
- NO puede modificar el estado de otros dominios de aislamiento.
Ejemplo de método sin estado (puede llamarse desde cualquier hilo):
// ⚠️ `nonisolated` es redundante porque es el dominio por defecto.
nonisolated func add(a: Int, b: Int) -> Int {
a + b
}
func add(a: Int, b: Int) -> Int {
a + b
}¿Por qué acceder a propiedades de un actor desde afuera requiere await?
Un actor garantiza que todas sus propiedades almacenadas y métodos se ejecuten en un entorno seguro de un solo hilo, evitando carreras de datos.
Para acceder a esos datos desde otro dominio, se necesita await para:
- Cambiar al contexto del actor.
- Esperar a que el actor libere su exclusividad.
actor Library {
// Propiedad aislada; solo se puede leer con `await`.
var books: [String] = []
// Métodos del actor (implícitamente async cuando se llaman desde fuera).
func addBook(_ title: String) {
books.append(title)
}
func getBookList() -> [String] {
books
}
// Método `nonisolated`: puede llamarse sin `await`.
nonisolated func libraryName() -> String {
"A library of books"
}
}Uso:
let library = Library()
// ❌ No se puede invocar un método del actor de forma síncrona.
library.addBook("hola") // Error de compilación
Task {
await library.addBook("Dopamine Nation")
let books = await library.getBookList()
print(books) // ["Dopamine Nation"]
// Lectura directa de la propiedad (requiere await).
await library.books.forEach { print($0) }
// ❌ No se puede mutar la propiedad fuera del actor.
// library.books.append("Another") // Error
}¿En qué se diferencia un global actor como @MainActor de un actor regular?
- Actor regular: cada instancia tiene su propio dominio de aislamiento.
- Global actor: el dominio es compartido entre todas las instancias marcadas con ese actor (p. ej.,
@MainActor). Es útil cuando varias partes de la aplicación deben operar bajo las mismas restricciones de concurrencia.
¿Qué condiciones debe cumplir una API pública para considerarse thread‑safe según el protocolo Sendable?
Una interfaz es segura entre dominios de aislamiento cuando:
- No expone modificadores mutables públicos.
- Implementa su propio mecanismo de bloqueo interno (p. ej.,
NSLock,DispatchQueue). - Sus modificadores utilizan copy‑on‑write (como los tipos de valor).
Muchos tipos de la biblioteca estándar ya conforman Sendable. El compilador también puede inferir conformidad implícita:
// ✅ Conformidad implícita porque todos sus miembros son Sendable.
struct SomeStruct {
var someIntValue: Int
}// ❌ No conforma implícitamente; las clases son tipos por referencia.
class SomeClass {
var someIntValue: Int = 0
}Recitación
¿Por qué un
structcon una propiedadIntconformaSendableimplícitamente, pero unaclasscon la misma propiedad no?
Porquestructes un tipo por valor; al copiarlo se crea una nueva instancia independiente, lo que evita carreras de datos. En cambio,classes por referencia y puede ser mutada simultáneamente desde varios dominios.¿Puedes explicar con tus propias palabras qué ocurre cuando un valor viaja entre dos dominios de aislamiento?
Swift verifica que el valor seaSendable. Si lo es, el compilador permite su traslado garantizando que no habrá acceso concurrente no sincronizado al mismo dato.¿Cuándo tendría sentido…? (continúa según el contexto que necesites).
¿Cómo marcar un método como nonisolated dentro de un actor?
Se puede marcar con nonisolated si no es necesario proteger el estado del actor. Esto ocurre, por ejemplo, cuando el método retorna algún valor escalar o constante.
Review
1. ¿Cuáles son las tres condiciones bajo las cuales una API pública es segura para usarse entre dominios de concurrencia?
- Inmutabilidad – Los valores expuestos no pueden modificarse después de su creación.
- Sendable – Todos los tipos involucrados conforman el protocolo
Sendable. - Sin efectos secundarios – La API no accede ni modifica estado externo que pueda ser compartido entre hilos.
2. ¿Qué cambia en Swift 6.2 respecto al comportamiento por defecto de Sendable?
Swift 6.2 introduce nonisolated(nonsending), que permite que funciones y closures no sean Sendable a menos que crucen una frontera de aislamiento. De este modo, el compilador ya no impone automáticamente la conformidad a Sendable en contextos nonisolated, facilitando la interoperabilidad con código que no necesita ser enviado entre dominios de concurrencia.
Bibliografía
- Van der Lee, A. (2025). Swift Concurrency Course [Curso en línea]. avanderlee.com.