[SC] Sendable

Published: (March 16, 2026 at 04:42 PM EDT)
5 min read
Source: Dev.to

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:

TipoDescripción
nonisolatedDominio por defecto; no impone restricciones de concurrencia.
actorCada instancia de actor tiene su propio dominio.
global actorUn dominio compartido por varios tipos, propiedades y funciones (p. ej. @MainActor).

¿Qué restricciones tiene el código nonisolated respecto al estado de otros dominios?

  • nonisolated es 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:

  1. Cambiar al contexto del actor.
  2. 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 struct con una propiedad Int conforma Sendable implícitamente, pero una class con la misma propiedad no?
    Porque struct es un tipo por valor; al copiarlo se crea una nueva instancia independiente, lo que evita carreras de datos. En cambio, class es 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 sea Sendable. 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?

  1. Inmutabilidad – Los valores expuestos no pueden modificarse después de su creación.
  2. Sendable – Todos los tipos involucrados conforman el protocolo Sendable.
  3. 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.
0 views
Back to Blog

Related posts

Read more »

Modern JS Talk async function

Overview An async function returns an Async Function object. By using the async and await keywords, asynchronous processing can be written more concisely than...

[SC] Ejecutando Tasks en SwiftUI

¿Qué relación tiene el ejemplo de búsqueda con .task? Se retoma el ejemplo de search_ query: String donde se filtran unas entradas pasado un rebote de 500 ms....