Sendable é um protocolo em Swift que indica que um tipo é seguro para compartilhar entre domínios de concorrência como atores, tarefas e threads. Quando um tipo adere ao Sendable, o compilador verifica no tempo de compilação que passá-lo não introduzirá corridas de dados. Juntamente com a atribuição @Sendable para fechamentos, é uma das principais estruturas da segurança contra corridas de dados do Swift.
Antes de mergulhar neste tópico sobre sendables, encorajo você a ler meus artigos sobre async/await, atores e isolação de atores. Esses artigos abordam os fundamentos das mudanças de concorrência, que se conectam diretamente com as técnicas explicadas neste artigo.
Quando devo usar Sendable?
O próprio protocolo Sendable é surpreendentemente simples:
public protocol Sendable {}
É um protocolo vazio sem requisitos. Sua única função é atuar como marcador: aderir informa ao compilador que seu tipo é seguro para ser passado entre domínios de isolamento.
O protocolo Sendable indica se o valor passado tem uma API pública segura em termos de concorrência. Uma API pública pode ser usada entre domínios de concorrência quando não há mutadores públicos, um sistema interno de bloqueio está em vigor ou os mutadores implementam cópia-on-write, como com tipos valor.
Muitos tipos da biblioteca padrão já suportam o protocolo Sendable, removendo a necessidade de adicionar conformidade para muitos tipos. Como resultado do suporte da biblioteca padrão, o compilador pode implicitamente criar suporte para seus próprios tipos personalizados.
Por exemplo, inteiros suportam o protocolo:
extension Int: Sendable {}
Ao criarmos uma estrutura de valor com um único atributo do tipo inteiro, implicitamente obtemos suporte para o protocolo Sendable:
// Implicitly conforms to Sendable
struct Article {
var views: Int
}
Ao mesmo tempo, a seguinte classe do exemplo do mesmo artigo não teria conformidade implícita:
// Does not implicitly conform to Sendable
class Article {
var views: Int
}
A classe não adere porque é um tipo de referência e, portanto, mutável de outros domínios concorrentes. Em outras palavras, a classe artigo não é segura para ser passada em torno de threads e o compilador não pode marcar implicitamente como Sendable.
Conformidade implícita ao usar genéricos e enums
É bom entender que o compilador não adiciona conformidade implícita a tipos genéricos se o tipo genérico não aderir ao Sendable.
// Sem conformidade implícita com Sendable porque Value não é conformante a Sendable
struct Container<Value> {
var child: Value
}
No entanto, se adicionarmos uma exigência de protocolo ao nosso valor genérico, teremos suporte implícito:
// Container tem conformidade implícita com Sendable pois todos os seus propriedades públicas também o fazem.
struct Container<Value: Sendable> {
var child: Value
}
O mesmo vale para enums com valores associados:
Pode-se ver que obtemos um erro do compilador:
O valor associado ‘loggedIn(name:)’ de enum ‘State’ com conformidade a Sendable tem tipo não sendável ‘(name: NSAttributedString)’
Pode-se resolver o erro usando um tipo de valor String, já que ele já é conformante a Sendable:
enum State: Sendable {
case loggedOut
case loggedIn(name: String)
}
Lançando erros de instâncias seguras em threads
As mesmas regras se aplicam a erros que desejam conformar-se a Sendable:
struct ArticleSavingError: Error {
var author: NonFinalAuthor
}
extension ArticleSavingError: Sendable { }
Como o autor é não final e não thread-safe (mais sobre isso depois), teremos o seguinte erro:
A propriedade armazenada ‘author’ de struct ‘ArticleSavingError’ com conformidade a Sendable tem tipo não sendável ‘NonFinalAuthor’
Pode-se resolver o erro garantindo que todos os membros de ArticleSavingError sejam conformes a Sendable.
Como usar o protocolo Sendable
A conformidade implícita tira muitos casos em que precisamos adicionar conformidade ao protocolo Sendable nós mesmos. No entanto, existem casos em que o compilador não adiciona conformidade implícita enquanto sabemos que nosso tipo é thread-safe.
Exemplos comuns de tipos que não são implicitamente sendáveis, mas podem ser marcados como tal, são classes isoladas a um ator global e classes imutáveis:
/// User é imutável e, portanto, thread-safe, então pode conformar-se ao Sendable
final class User: Sendable {
let name: String
init(name: String) { self.name = name }
}
Existe uma terceira maneira de tornar uma classe Sendable: isolá-la a um ator global. Uma classe anotada com @MainActor é implicitamente Sendable, já que o ator garante acesso serializado ao seu estado mutável. É por isso que modelos de visualização isolados no ator principal podem ser passados livremente, mesmo quando contêm propriedades mutáveis.
A restrição de conformar-se ao Sendable no mesmo arquivo-fonte
A conformidade com o protocolo Sendable deve ocorrer dentro do mesmo arquivo-fonte para garantir que o compilador verifique todos os membros visíveis pela segurança em threads.
Por exemplo, você poderia definir o seguinte tipo dentro de um módulo como uma pacote Swift:
public struct Article {
internal var title: String
}
O artigo é público, enquanto o título é interno e não visível fora do módulo. Portanto, o compilador não pode aplicar conformidade ao Sendable fora do arquivo-fonte, mesmo que o título esteja usando um tipo Sendable String.
O mesmo problema ocorre quando tentamos fazer uma classe imutável e não final conformar com Sendable:
Como a classe é não final, não podemos nos conformar com Sendable, pois não sabemos se outras classes herdarão de User com membros que não são Sendable. Portanto, teríamos o seguinte erro:
A classe não final ‘User’ não pode se conformar a `Sendable`; use `@unchecked Sendable`
Como você pode ver, o compilador sugere usar @unchecked Sendable. Podemos adicionar esse atributo à nossa instância de usuário e remover o erro:
class User: @unchecked Sendable {
let name: String
init(name: String) { self.name = name }
}
No entanto, isso exige que garantamos a segurança em threads sempre que herdar de User. Como estamos adicionando mais responsabilidade para nós e nossos colegas, eu desencorajo o uso deste atributo em favor da composição, classes finais ou tipos de valor.
Funções podem ser passadas entre domínios de concorrência e, portanto, também exigem conformidade com Sendable. No entanto, funções não podem se conformar a protocolos, então o Swift introduziu o atributo @Sendable. Exemplos de funções que você pode passar ao redor são declarações globais de função, fechamentos e acessores como getters e setters.
Parte da motivação para SE-302 é realizar a menor sincronização possível:
nós queremos que a grande maioria do código em tal sistema seja sem sincronização
Usando o atributo @Sendable, diremos ao compilador que ele não precisa de sincronização adicional porque todos os valores capturados no fechamento são thread-safe. Um exemplo típico seria usar fechamentos dentro do isolamento de atores:
actor ArticlesList {
func filteredArticles(_ isIncluded: @Sendable (Article) -> Bool) async -> [Article] {
// ...
}
}
No caso de você usar o fechamento com um tipo não sendable, teríamos um erro:
let listaDeArtigos = ArticlesList()
var termoBusca: NSAttributedString? = NSAttributedString(string: "palavra-chave")
let artigosFiltrados = await listaDeArtigos.filteredArticles { artigo in
// Erro: Referência à variável capturada 'termoBusca' em código de execução concorrente
guard let termoBusca = termoBusca else { return false }
return artigo.titulo == termoBusca.string
}
Claro, podemos resolver rapidamente esse caso usando uma string regular ao invés disso, mas isso demonstra como o compilador ajuda a garantir a segurança de threads.
Como usar @unchecked Sendable
@unchecked Sendable informa ao compilador para pular a verificação completamente: você assume a responsabilidade de garantir a segurança de threads. É uma saída valiosa para código legado com mecanismos internos de bloqueio, mas deve ser sua última opção.
Um exemplo típico é uma classe que sincroniza o acesso usando um bloqueio:
final class UsuarioMutavel: @unchecked Sendable {
private let bloqueio = NSLock()
private var nome: String = ""
func atualizarNome(_ nome: String) {
bloqueio.lock()
defer { bloqueio.unlock() }
self.nome = nome
}
}
O compilador não pode verificar que essa classe é segura para threads, mas sabemos disso devido ao bloqueio. O risco é real, no entanto: adicione uma propriedade que não use o bloqueio e você introduziu silenciosamente um potencial conflito de dados sem nenhum suporte do compilador.
Você pode não precisar mais de @unchecked
Desde a chegada do framework de sincronização, você pode frequentemente escrever a mesma classe como uma conformidade regular, verificável Sendable:
import Synchronization
final class UsuarioMutavel: Sendable {
private let nome = Mutex("")
func atualizarNome(_ nome: String) {
self.nome.withLock { $0 = nome }
}
}
Mutex é Sendable por si só, e o compilador pode agora verificar todas as propriedades armazenadas. Em outras palavras: segurança total contra conflitos de dados, sem promessas não verificáveis. Para novos códigos, recomendo usar um ator ou Mutex antes de recorrer a @unchecked Sendable.
Envio de valores não Sendable entre domínios de isolamento
Não é necessário que todos os tipos se conformem com Sendable para cruzar uma fronteira de isolamento. Desde o Swift 6, o compilador realiza análise baseada em região: se ele puder provar que ninguém mais acessa um valor depois que você o passa adiante, a transferência é segura, mesmo para tipos não Sendable.
class Article {
var title: String
init(title: String) { self.title = title }
}
func check() {
let article = Article(title: "Swift")
Task {
print(article.title) // OK: o compilador prova acesso exclusivo
}
}
No entanto, assim que você acessar o valor após a transferência, o compilador intervém:
func check() {
let article = Article(title: "Swift")
Task {
print(article.title)
}
print(article.title) // Erro: 'article' foi enviado para a tarefa
}
A palavra-chave de envio
Você pode tornar essa transferência de propriedade explícita em suas APIs usando a palavra-chave sending:
actor Logger {
func log(article: Article) {
print(article.title)
}
}
func printTitle(article: sending Article) async {
let logger = Logger()
await logger.log(article: article)
}
O parâmetro sending informa aos chamadores que eles estão transferindo a propriedade: após passar o artigo, eles não podem mais usá-lo. Isso permite que APIs aceitem tipos não Sendable de forma segura e é por isso que você verá sending aparecendo em mais e mais APIs da biblioteca padrão.
Verificação estrita de concorrência e o modo de linguagem Swift 6
A verificação estrita de concorrência começou como uma configuração de construção opcional e se tornou padrão no modo de linguagem Swift 6. Se seu projeto ainda está sendo construído com o modo de linguagem Swift 5, você pode optar gradualmente usando a configuração de construção SWIFT_STRICT_CONCURRENCY com seus níveis minimal, alvo e completo. Uma vez que você se move para o modo de linguagem Swift 6, a verificação completa está sempre ativa e problemas de corrida de dados tornam-se erros em vez de avisos. Eu cobri esse processo detalhadamente em Swift 6: O que há de Novo e Como Migrar.
Habilitando Concorrência Estrita no Xcode
O Xcode permite habilitar a verificação de concorrência estrita através da configuração de build SWIFT_STRICT_CONCURRENCY:

Esta configuração de build controla o nível de aplicação das verificações do compilador de conformidade Sendable e isolamento de atores.
- Mínimo: O compilador diagnosticará apenas instâncias explicitamente marcadas com conformidade Sendable, igualando-se ao comportamento do Swift 5.5 e 5.6. Não haverá avisos ou erros.
- Alvoado: Aplique as restrições de Sendable e verifique o isolamento de atores para todo o seu código que adotou concorrência como async/await. O compilador também verificará instâncias que explicitamente adotam Sendable. Este modo tenta equilibrar a compatibilidade com códigos existentes e detectar possíveis corridas de dados.
- Completo: Efetua verificações para corresponder aos semânticos pretendidos do Swift 6, eliminando as corridas de dados. Este modo faz tudo o que os dois modos anteriores fazem mas realiza essas verificações em todo o código do seu projeto.
A configuração de build para verificação de concorrência estrita ajuda o Swift a avançar na segurança contra corridas de dados. Cada um dos avisos disparados relacionados a esta configuração pode indicar uma possível corrida de dados no seu código. Portanto, é essencial considerar a habilitação da verificação de concorrência estrita para validar o seu código.
A quantidade de avisos que você receberá depende da frequência com que usou a concorrência em seu projeto. Para o Stock Analyzer, eu tive cerca de 17 avisos para resolver:

Esses avisos podem ser intimidantes, mas com o conhecimento deste artigo, você deve conseguir resolver a maioria deles e prevenir que ocorram corridas de dados. No entanto, alguns avisos estão fora do seu controle, pois são disparados por um módulo externo. No meu caso, tive um aviso relacionado ao SWHighlight, que não segue o padrão Sendable, enquanto a Apple definiu isso em sua própria SharedWithYou.
O compilador disparou vários avisos relacionados a esse mesmo problema:
- Captura de ‘highlight’ com tipo não enviável ‘SWHighlight?’ em uma clausula
@Sendable - Propriedade armazenada ‘highlight’ da estrutura que segue o padrão Sendable ‘SharedSymbol’ tem tipo não enviável ‘SWHighlight?’
Uma maneira de resolver isso seria adicionar conformidade com Sendable nós mesmos:
extension SWHighlight: Sendable { }
No entanto, você encontrará o seguinte erro:
A conformidade com ‘Sendable’ deve ocorrer no mesmo arquivo de origem da classe ‘SWHighlight’; use ‘@unchecked Sendable’ para conformidade retroativa
Seguindo a sugestão, você alteraria o código da seguinte forma:
extension SWHighlight: @unchecked Sendable { }
O atributo @unchecked informa ao compilador para desativar a verificação de concorrência para instâncias de SWHighlight. Embora isso remova os avisos que vimos anteriormente, não resolve as condições potenciais de corridas. É melhor usar o atributo @unchecked apenas para tipos de referência que fazem sua própria sincronização interna sem atores. Esses tipos são thread-safe, mas não há como o compilador verificar isso. Você pode marcá-los como desativados, informando ao compilador que você cuidará das corridas de dados por conta própria.
No exemplo acima do framework SharedWithYou, é melhor esperar pelos proprietários da biblioteca para adicionar suporte Sendable. Nesse caso, isso significaria esperar pela Apple para indicar conformidade Sendable para instâncias de SWHighlight. Para essas bibliotecas, você pode temporariamente desativar os avisos Sendable fazendo uso do atributo @preconcurrency:
@preconcurrency import SharedWithYou
É importante entender que não resolvemos os avisos, mas apenas desativamos eles. Ainda há a possibilidade de ocorrerem corridas de dados com o código dessas bibliotecas. Se você estiver usando instâncias desses frameworks, precisa considerar se as instâncias são realmente thread-safe. Uma vez que seu usado framework seja atualizado com conformidade Sendable, você pode remover o atributo @preconcurrency e corrigir os avisos potencialmente disparados.
Continuando sua jornada em Swift Concurrency
As mudanças de concorrência são mais do que apenas async-await e incluem muitos novos recursos pelos quais você pode se beneficiar no seu código. Agora que você aprendeu sobre Sendable, é hora de mergulhar em outros recursos de concorrência:
Conclusão
Sendable e @Sendable são a maneira do compilador de verificar que valores cruzando domínios de concorrência não podem introduzir corridas de dados. Em maioria das situações, conformidade implícita e isolamento baseado em região fazem o trabalho por você. Quando isso não ocorre, agora você sabe a ordem para tentar: tipos de valor, atores, Mutex e apenas então @unchecked Sendable.
Problemas com Sendable são onde a maioria das migrações do Swift 6 fica presa. No meu Curso de Swift Concurrency, dedico um módulo inteiro ao Sendable, incluindo as estratégias de migração que usei em meus próprios aplicativos.
Se você gostaria de aprender mais dicas sobre Swift, verifique a página da categoria Swift. Sinta-se à vontade para entrar em contato comigo ou tweetar para mim no Twitter se você tiver alguma dica adicional ou feedback.
Obrigado!

