Sendable é um protocolo Swift que marca tipos seguros para transferência entre threads. Ao trabalhar com concorrência (actor, async/await, Task), o compilador Swift exige que todos os dados transferidos entre contextos isolados estejam em conformidade com Sendable. Isso evita a transferência acidental de tipos inseguros que podem levar a condições de corrida. O protocolo atua como um contrato: um tipo em conformidade com Sendable garante a ausência de estado interno não sincronizado. De acordo com WWDC 2021, o protocolo Sendable é um elemento obrigatório ao projetar uma arquitetura segura multithread.
Principais conclusões
Sendable é um protocolo da biblioteca padrão Swift (SE-0302) que marca tipos seguros para transferência entre domínios de isolamento. Um domínio de isolamento significa um actor, uma tarefa Task ou um contexto @MainActor. Sendable não contém requisitos — atua como um marcador de segurança.
Antes do Swift 5.5, os desenvolvedores podiam transferir qualquer objeto entre threads via DispatchQueue, e o compilador não verificava se era seguro. Sendable fecha essa lacuna: agora o compilador rastreia as transferências entre threads e bloqueia as inseguras. Isso torna o código concorrente mais confiável mesmo antes de ser executado.
struct UserProfile: Sendable {
let name: String
let age: Int
}
class NonSendableClass {
var counter: Int = 0
}
No exemplo, UserProfile pode ser transferido com segurança entre threads porque é uma struct com propriedades constantes de tipos Sendable. NonSendableClass causará um erro de compilação ao tentar transferi-lo via await ou para um Task.
Os tipos Sendable em Swift dividem-se em três categorias: tipos de valor com propriedades Sendable, classes finais com estado imutável e funções/closures marcadas com @Sendable. O compilador infere automaticamente a conformidade com Sendable para tipos de valor.
Struct, enum e tupla tornam-se automaticamente Sendable se todas as suas propriedades e valores associados também forem Sendable. Esta é uma abordagem conservadora: se pelo menos uma propriedade não estiver em conformidade com Sendable, o tipo inteiro não será reconhecido como seguro.
| Tipo | Sendable | Condição |
|---|---|---|
| Struct | Sim (sem declaração explícita) | Todas as propriedades são Sendable |
| Enum | Sim (sem declaração explícita) | Todos os valores associados são Sendable |
| Final class | Sim (com declaração explícita) | Todas as propriedades let são Sendable, sem var |
| Non-final class | Não | Não pode ser Sendable devido à herança |
Todos os tipos incorporados do Swift — Int, String, Double, Bool, Optional, Array, Dictionary, Set — estão em conformidade com Sendable. Isso torna a composição de tipos segura por padrão. O desenvolvedor só precisa cuidar das classes personalizadas.
@unchecked Sendable é um mecanismo que permite a uma classe declarar-se explicitamente como Sendable, ignorando as verificações do compilador. O desenvolvedor assume a responsabilidade pela segurança de threads dessa classe. Isso é útil para pontes Objective-C e estruturas otimizadas.
@unchecked Sendable é usado quando uma classe garante internamente a segurança por meio de locks ou operações atômicas, mas o compilador não pode verificar isso estaticamente. Por exemplo, uma classe com os_unfair_lock ou pthread_mutex_t — sua segurança de threads é garantida pelo código, mas o Swift não vê isso.
final class AtomicCounter: @unchecked Sendable {
private var value: Int = 0
private let lock = NSLock()
func increment() {
lock.lock()
value += 1
lock.unlock()
}
}
Usar @unchecked Sendable deve ser feito com cuidado. É um sinal explícito para outros desenvolvedores: «verifiquei, é seguro». Um erro na implementação pode levar a condições de corrida difíceis de encontrar. Antes de usar @unchecked, certifique-se de que o tipo realmente não pode ser reescrito como um tipo de valor.
Actor e Sendable são dois lados da mesma moeda. Actor isola seu estado, mas para trocar dados com o mundo exterior deve retornar tipos Sendable. Se um método de actor retornar um tipo não Sendable, o compilador emite um aviso ou erro.
Quando um actor envia dados para código externo, esses dados cruzam o limite de isolamento. Sendable garante que o destinatário possa usá-los com segurança fora do actor. O actor permanece isolado — seu estado interno não é exposto.
struct AccountSnapshot: Sendable {
let id: UUID
let balance: Double
let lastUpdated: Date
}
actor BankActor {
private var balance: Double = 0
func snapshot() async -> AccountSnapshot {
return AccountSnapshot(
id: UUID(),
balance: balance,
lastUpdated: Date()
)
}
}
AccountSnapshot é uma struct Sendable que contém apenas propriedades let de tipos Sendable. Essa abordagem é uma boa prática para extrair dados de um actor. O snapshot do estado é transferido por valor, e o actor não perde o controle sobre seu estado.
@Sendable é um atributo para funções e closures que garante que a closure não capture dados não Sendable de forma mutável. Quando uma closure é transferida para um Task ou método de actor, ela deve ser Sendable.
O compilador verifica que a closure @Sendable não captura referências mutáveis para classes. Capturar propriedades let de tipos Sendable é permitido. Capturar uma variável var de um tipo de referência causará erro porque a closure pode ser executada simultaneamente com a mutação.
func performAsync(operation: @Sendable () -> Void) {
Task {
await operation()
}
}
let constant = "Safe"
var counter = 0
// counter — capturar var muta counter, erro de compilação
performAsync { // ❌ Mutação de variável capturada
print(constant)
}
A regra é simples: uma closure @Sendable só pode capturar dados que são eles próprios Sendable e não serão mutados externamente. Para classes, é permitido capturar uma referência fraca weak self se a classe não estiver marcada como Sendable. Isso evita ciclos de retenção clássicos e condições de corrida.
Perguntas frequentes
Sendable é um marcador que diz ao compilador: «este tipo pode ser transferido com segurança entre threads». Structs e constantes geralmente são Sendable por padrão, classes não, a menos que explicitamente indicado.
Tipos de valor (struct, enum) com propriedades Sendable, classes finais com estado imutável e todos os tipos básicos Swift: Int, String, Double, Bool, Array, Dictionary, Optional — estão em conformidade com Sendable automaticamente.
@unchecked Sendable é uma forma de declarar uma classe como Sendable sem verificações estáticas do compilador. O desenvolvedor garante a segurança por conta própria, muitas vezes através de locks ou operações atômicas.
Actor isola seu estado, mas ao retornar dados para código externo, esses dados cruzam o limite de isolamento. Sendable garante que o destinatário não encontrará condições de corrida ao usar esses dados fora do actor.
Adicione o protocolo Sendable a uma classe final, cujas propriedades sejam todas constantes de tipos Sendable. Se a classe usar locks, pode-se usar @unchecked Sendable, mas isso requer cautela.
Resumo
Vamos desenvolver um aplicativo móvel chave na mão
A IT Sectr cria aplicativos para iOS e Android para startups e empresas desde 2017. Nós vamos aconselhá-lo e propor a melhor solução.
Leia também