O protagonista da segunda parte da série Swift Concurrency é actor, um quarto tipo, diferente de class e struct.
A necessidade de um novo tipo indica um problema sério. Vamos começar pelas condições de corrida.
Condições de corrida — o pior tipo de bug
As condições são claras: duas ou mais threads acessam a mesma memória ao mesmo tempo, e pelo menos um acesso escreve.
Quando isso ocorre, o comportamento é indefinido. Valores podem ser corrompidos, o app pode travar ou nada parecer acontecer.
O resultado depende da sorte do escalonamento naquele dia.
final class Counter {
var value = 0
func increment() { value += 1 } // etapas de ler-somar-escrever, 3
}
// quando duas threads chamam simultaneamente increment()
// 1000mesmo que seja chamado valuepode não ser 1000
value += 1não é atômico. Se outra thread entrar entre ler, somar e escrever, o incremento desaparece.
Esse bug é perverso porque é difícil reproduzir. Depende do timing: os testes passam e, depois do lançamento, chegam relatos de travamentos intermitentes.
Na parte sobre tipos por valor, vimos que eles não são compartilhados e eliminam a premissa da corrida. Restam os tipos por referência, feitos para compartilhar.
A solução tradicional era usar locks, semáforos e uma DispatchQueue serial.
Todos funcionam, mas dependem totalmente da disciplina do desenvolvedor.
Basta esquecer o lock em um lugar; o compilador não consegue enxergar esse erro.
Esse cenário é familiar. Assim como a checagem de nil na parte de optionals e os ciclos de referência na parte de ARC, Swift leva ao sistema de tipos o que dependia de disciplina.
Agora é a vez das condições de corrida.
actor — o tipo que protege o próprio estado
Em uma frase, actor é um tipo por referência com proteção de estado integrada.
actor Counter {
var value = 0
func increment() { value += 1 }
}
Trocar class por actor garante que apenas uma execução por vez acesse suas propriedades armazenadas.
Cada actor tem um executor serial; as chamadas entram em fila e são processadas uma a uma. Nada interrompe os três passos de increment, então a corrida não ocorre.
O ponto central é a imposição pelo compilador. Fora do actor, acessar seu estado ou métodos exige await para compilar.
let counter = Counter()
await counter.increment() // fora await obrigatório
print(await counter.value)
await existe pelo mesmo conceito de suspensão explicado na parte 1. Se o actor estiver ocupado, a solicitação entra na fila e cede sem prender uma thread.
Não é como um lock, que deixa a thread dormindo. A função é suspensa e retomada quando chega sua vez.
Dentro do actor, o código acessa tudo livremente sem await, pois já está isolado.
Essa fronteira entre dentro e fora é o isolamento apresentado na parte 1.
@MainActor é a versão global: transforma a thread principal em um contexto serial e isola o estado da UI.
O princípio é igual ao de um actor personalizado.
reentrada — a armadilha mais conhecida de actor
actor parece万能, mas tem uma armadilha de projeto: permite reentrada.
Ao encontrar await dentro de um método de actor, ele é suspenso e o actor fica disponível. Outra chamada pode entrar e executar nesse intervalo.
Quando o método original retoma, o estado do actor pode estar diferente de antes da suspensão.
actor ImageCache {
var cache: [URL: Image] = [:]
func image(for url: URL) async -> Image {
if let cached = cache[url] { return cached }
let image = await download(url) // ponto de suspensão — outra chamada pode entrar nesse intervalo
cache[url] = image // o mesmo URLpode já estar armazenado
return image
}
}
Se duas solicitações para a mesma URL chegarem quase juntas, ambas veem cache miss e fazem o download. Os dados não se corrompem —o actor impede isso—, mas a lógica executa duas vezes.
A regra é: actor impede condições de corrida de baixo nível, mas não garante consistência lógica através de await.
Ainda é preciso rever as suposições de estado antes e depois de await. Aqui, guardar o Task de download em andamento no cache evita duplicidade.
Por quê? Proibir reentrada exigiria bloquear o actor durante await, possibilitando deadlock eterno entre dois actors que esperam um pelo outro.
Swift escolhe um sistema sem deadlock e deixa a consistência lógica para o desenvolvedor. Entender essa troca torna a armadilha previsível.
Uso prático — onde usar actor
actor serve como proprietário de estado mutável compartilhado por vários contextos assíncronos.
Caches, pools de conexão, gerenciadores de download e armazenamentos de sessão. Onde houver a pergunta “quem protege este estado?”, actor é candidato.
Também há casos inadequados. Primeiro: estado não compartilhado.
Para um view model usado em uma tela, uma classe @MainActor é adequada. Um actor personalizado só adiciona await desnecessários ao acesso à UI.
Segundo: dados imutáveis. Sem corrida, struct ou let bastam — prioridade para tipos por valor.
Terceiro: hot paths extremos. Atravessar o limite do actor custa serialização; centenas de milhares de chamadas por segundo pedem uma revisão do projeto.
Mais um ponto: actor pode ser exagero para proteger um único contador ou sinalizador atômico.
Nesses casos, uma ferramenta de baixo nível como Mutex, do módulo Synchronization do Swift 6, é mais leve e dispensa await. actor brilha quando estado e lógica formam um conjunto.
Resumo
- Uma condição de corrida é comportamento indefinido causado por acesso concorrente e pelo menos uma escrita; é perversa porque não reproduz facilmente. Locks funcionam, mas dependem de disciplina.
- actor é um tipo por referência com proteção integrada. Seu executor serializa o acesso, e o compilador exige await fora dele.
- @MainActor transforma a thread principal em um actor global e é o padrão para isolamento de UI.
- actor permite reentrada. O estado pode mudar através de await; a consistência lógica ainda precisa ser garantida manualmente.
- Use actor como proprietário de estado mutável compartilhado. É exagero para estado não compartilhado, dados imutáveis e hot paths extremos.
A próxima parte aborda Sendable, a última peça do isolamento: tipos seguros ao cruzar limites e como interpretar os avisos de strict concurrency do Swift 6 no código existente.

![Imagem de capa de [Swift avançado #2] actor do Swift: guia completo para evitar condições de corrida](/assets/images/posts/d7809f68-f42a-4283-a270-2c25867c9087/swift-actor-1.jpg)