Se sua equipe ativou o modo Swift 6, provavelmente se lembra do momento: um projeto que funcionava normalmente passa a exibir dezenas ou centenas de erros de concorrência.
O protagonista da maioria das mensagens de erro é um só: Sendable.
A terceira parte da série Concurrency organiza esta última peça do quebra-cabeça e a strict concurrency do Swift 6.
A pergunta de Sendable — este valor pode atravessar o limite?
No artigo sobre actor, definimos isolamento como um “contexto de execução serial”: dentro de um actor, sobre o MainActor e em um pool cooperativo que não pertence a nenhum dos dois.
Mas os valores atravessam esses limites o tempo todo. Eles entram como argumentos de métodos de actor, saem como valores de retorno e são capturados por closures de Task.
É aí que surge o risco. O isolamento protege o estado do próprio actor, mas e se o valor que atravessa o limite for um tipo por referência mutável?
A mesma instância pode ficar nas mãos de dois isolamentos ao mesmo tempo, e a condição de corrida de dados que o actor bloqueava volta por meio do objeto trazido. É como ter uma fiscalização de fronteira rigorosa sem verificar as mercadorias que entram.
Sendable define esse critério de inspeção. É um protocolo marcador, sem métodos obrigatórios, e seu significado é simples:
Valores desse tipo podem ser usados simultaneamente com segurança depois de atravessar um limite de isolamento.
Quais tipos são seguros? A intuição é direta.
Tipos por valor são copiados ao atravessar o limite, então são seguros se todas as propriedades armazenadas forem Sendable. Int, String e structs e enums compostos apenas por propriedades Sendable entram aqui. Na maioria dos casos, o compilador os reconhece automaticamente.
Actors também são seguros, pois a autoproteção já vem integrada.
Classes imutáveis (final e apenas com propriedades let) também são seguras. Se nada pode mudar, não há corrida.
Há exatamente um perigo: classes com estado mutável. A fórmula do artigo sobre priorizar tipos por valor, “compartilhado + mutável = perigoso”, é o próprio critério de Sendable.
Tipos de função têm uma anotação @Sendable específica. A closure de Task { } é uma closure @Sendable típica, e uma closure com essa marca não pode capturar valores non-Sendable.
Como vimos no artigo sobre closures, capturas podem virar rotas de contrabando através do isolamento; por isso a linguagem verifica a própria rota.
strict concurrency — De avisos a erros, de disciplina a verificação
Essas regras já existiam antes do Swift 6, mas ficavam silenciosas por padrão.
O ponto central do modo de linguagem Swift 6 é ativar todas essas verificações e promovê-las a erros: strict concurrency. Com complete checking, todo ponto em que um valor non-Sendable atravessa um limite de isolamento vira erro de compilação.
Os erros não aparecem porque o código ficou pior de repente. Apenas os potenciais data races que já existiam agora ficaram visíveis.
É como quando os optionals foram introduzidos e todos os lugares onde um valor podia ser nil vieram à tona. Assim como os nil checks naquela época, agora as suposições sobre concorrência migram para o sistema de tipos.
Felizmente, o compilador também está ficando mais inteligente. O Swift 6 inclui a análise de isolamento baseada em regiões (proposta Swift Evolution SE-0414, region-based isolation).
Até valores non-Sendable podem ser movidos quando se prova que “quem os enviou não vai tocá-los novamente”.
Um valor cuja propriedade foi transferida não pode criar uma corrida. Por isso, muito código que teoricamente deveria falhar na prática passa.
A anotação sending em parâmetros segue a mesma direção. Em resumo, a regra está ficando mais precisa: de “sempre proibido” para “permitido quando a segurança é comprovada”.
Migração na prática — Soluções por tipo de erro
A maioria dos erros se resume a alguns padrões. Estas são as soluções padrão para cada tipo.
Tipo 1. Meu modelo é non-Sendable. É o erro mais comum e mais saudável. A primeira solução é transformá-lo em struct.
Se um modelo de dados que não precisa de identidade de referência foi declarado como class, esta é a oportunidade de migrá-lo para um tipo por valor.
Se ele precisa ser class, torne-o imutável com final + let e adote Sendable. Se precisa ser mutável, isso significa que o estado precisa de um proprietário; considere promovê-lo a actor.
Tipo 2. Variáveis globais e static var. Todos os locais que alertávamos como “static var é, na prática, estado global” viram erros.
Se você realmente precisa de estado global, declare o isolamento. Adicione @MainActor para o que envolve UI; caso contrário, envolva em um actor ou torne-o imutável com let.
Tipo 3. Uma classe de delegate ou callback fica presa no limite. Isso aparece com frequência nos pontos que encontram APIs da era do UIKit.
Se o tipo é essencialmente exclusivo da thread principal, declarar @MainActor costuma ser a resposta. Você transforma em declaração explícita o fato implícito de que “essa classe sempre foi usada na thread principal”.
Tipo 4. É realmente seguro, mas o compilador não sabe. Por exemplo, classes protegidas internamente por locks e wrappers de bibliotecas C.
A saída é @unchecked Sendable. Ela declara “eu garanto que é seguro, então desative a verificação”, mas unchecked é uma ferramenta da família unsafe, como o nome indica.
Seguindo o princípio da saída explícita do primeiro artigo de Filosofia, deixe em comentário a base da garantia (qual lock protege o quê) e use-a no menor escopo possível.
Se você começar a cobrir erros com unchecked por conveniência da migração, terminará com código sem verificações, mas usando o selo do Swift 6.
Estrategicamente, não é preciso ativar tudo de uma vez. O modo de linguagem pode ser escolhido por módulo.
A abordagem padrão é de baixo para cima: migrar primeiro para o modo Swift 6 os módulos folha com poucas dependências, como utilitários e modelos, e deixar o target do app por último.
Os sinalizadores de upcoming feature do Xcode também ajudam a elevar apenas o nível de verificação antecipadamente e observar os avisos.
Entendendo a direção — por que passar por tudo isso?
Como a dor da migração é real, é importante entender exatamente o que justifica esse custo.
A promessa do Swift 6 é: se compila, não há data races. Categorias inteiras de crashes intermitentes irreproduzíveis e bugs de timing que só aparecem após o lançamento desaparecem em tempo de compilação.
É o mesmo caminho percorrido pela segurança de memória (optionals, ARC — Automatic Reference Counting, contagem automática de referências). A segurança de concorrência passa de “algo que se resolve escrevendo bem” para “algo garantido pela linguagem”.
Essa direção também estende a trajetória da série de filosofia: promover regras sujeitas a erros para o sistema de tipos, exigir anotações explícitas onde há custo (@unchecked, sending) e absorver o atrito da transição com adoção gradual (modos de linguagem por módulo).
Os procedimentos vistos no artigo sobre Swift Evolution continuam reduzindo o atrito ao adicionar mecanismos de amortecimento, como opções de isolamento padrão. Os avisos que você enfrenta agora são o ponto intermediário dessa transição.
Resumo
- Sendable marca tipos cujos valores podem ser usados simultaneamente com segurança ao atravessar limites de isolamento. Tipos por valor, actors e classes imutáveis são seguros; classes mutáveis concentram todo o perigo.
- Closures @Sendable verificam suas capturas e impedem que se tornem rotas de contrabando para corridas.
- Swift 6 strict concurrency promove essas verificações a erros. A explosão de erros não significa que o código piorou; significa que corridas potenciais vieram à tona.
- Prioridade: transformar em struct → tornar imutável → promover a actor → declarar isolamento (@MainActor) → por fim, usar @unchecked Sendable com a justificativa documentada.
- Migre de baixo para cima, começando pelos módulos folha, e eleve o modo de linguagem módulo a módulo.
A próxima parte encerra a série Concurrency com concorrência estruturada. Ela aborda a árvore de tarefas formada por Task, async let e TaskGroup, e como cancellation se propaga por essa árvore.

![Imagem de capa de [Swift avançado #3] Migração de Sendable e dos erros de concorrência do Swift 6](/assets/images/posts/053e9ee5-0023-4c58-838f-c7d6d31cf70e/swift-sendable-strict-concurrency-1.jpg)