@MainActor é um ator global na linguagem Swift que garante a execução de código na thread principal. De acordo com Apple Developer, 2024, @MainActor automatiza a troca para a thread principal ao trabalhar com a UI, liberando o desenvolvedor de chamar manualmente DispatchQueue.main.async. A anotação apareceu no Swift 5.5 junto com o sistema async/await.
Pontos principais
@MainActor é um ator global em Swift que combina as propriedades dos atores com a garantia de execução na thread principal da aplicação. Faz parte do sistema de concorrência Swift apresentado no Swift 5.5 junto com async/await e concorrência estruturada. A anotação permite que o desenvolvedor não se preocupe com a troca manual de threads e reduz o número de erros de UI.
Um ator em Swift é um tipo por referência que isola seu estado e garante que apenas uma thread possa modificá-lo. @MainActor é um ator global especial cujo executor é a thread principal. Qualquer código marcado com @MainActor é executado na thread principal — mesmo se chamado de uma tarefa em segundo plano.
Antes do @MainActor, os desenvolvedores trocavam manualmente para a thread principal usando DispatchQueue.main.async. Isso era uma fonte frequente de erros: os desenvolvedores esqueciam de trocar, causando crashes devido a atualizações de UI fora da thread principal. @MainActor resolve esse problema no nível do sistema de tipos.
A fonte da maioria dos bugs em aplicativos iOS é a insegurança da UI — atualizar a interface de uma thread secundária. A Apple incorporou @MainActor no Swift Concurrency para tornar a troca para a thread principal automática e verificável pelo compilador, eliminando toda uma classe de erros de runtime.
O princípio de funcionamento do @MainActor é baseado no sistema de execução do Swift Concurrency. Quando uma thread chama uma função marcada com @MainActor, o escalonador a suspende no executor atual e a retoma na thread principal. O compilador rastreia os limites da chamada e garante a segurança.
A execução do @MainActor é gerenciada por MainActor.shared — um executor associado à thread principal da aplicação. Quando uma função assíncrona é marcada com @MainActor, ela sempre retoma neste executor, independentemente da thread em que a tarefa original foi iniciada.
import SwiftUI
class ViewModel: ObservableObject {
@Published var items: [String] = []
@MainActor
func loadData() async {
let result = await fetchRemoteData()
items = result // com segurança, MainActor garante a thread principal
}
}
Se uma função é marcada com @MainActor e chama outra função assíncrona, ela herda o contexto do ator por padrão. Isso significa que todas as chamadas aninhadas também executam na thread principal, a menos que especificado de outra forma. O compilador rastreia isso e emite um erro ao tentar passar um fechamento inconsistente.
Comparar @MainActor e DispatchQueue.main ajuda a entender por que o novo mecanismo é considerado mais seguro e conveniente, embora ambos resolvam a mesma tarefa — executar código na thread principal.
@MainActor é uma verificação no nível do compilador. Se você tentar chamar uma função @MainActor de um contexto inseguro, o compilador emitirá um aviso ou erro. DispatchQueue.main.async é uma chamada em tempo de execução: o código compilará, mas pode falhar em runtime ao tentar atualizar a UI a partir de uma thread secundária.
DispatchQueue.main.async adiciona um bloco à fila que pode ser executado com atraso. @MainActor com async/await realiza a troca direta de executor sem criar fechamentos desnecessários. Isso reduz a sobrecarga e torna o tempo de execução mais previsível.
// Abordagem antiga
DispatchQueue.main.async {
self.updateUI()
}
// Nova abordagem com @MainActor
@MainActor
func updateUI() {
// executa na thread principal
self.label.text = "Atualizado"
}
| Critério | @MainActor | DispatchQueue.main |
|---|---|---|
| Verificação | compilador | runtime |
| Sintaxe | anotação (declarativa) | chamada (imperativa) |
| Sobrecarga | baixa (troca de executor) | média (fechamento + fila) |
| Testabilidade | alta (MainActor.shared pode ser substituído) | baixa (difícil de mockar) |
Em projetos iOS reais, @MainActor é usado em camadas ViewModel, vistas SwiftUI e controladores UIKit. A anotação pode ser aplicada tanto a métodos individuais quanto a todo o tipo.
Ao marcar uma classe com @MainActor, você garante que todos os seus métodos e propriedades são acessíveis apenas na thread principal. Isso é especialmente conveniente para vistas SwiftUI e classes ObservableObject: você simplesmente adiciona @MainActor antes da classe, e todas as propriedades @Published atualizam com segurança.
@MainActor
final class UserListViewModel: ObservableObject {
@Published var users: [User] = []
@Published var isLoading = false
func fetchUsers() async {
isLoading = true
users = await api.getUsers()
isLoading = false
}
}
Ao trabalhar com código UIKit legado onde a troca de threads era manual, você pode usar MainActor.run para troca explícita. Isso é conveniente para uma transição incremental para Swift Concurrency sem reescrever toda a base de código.
await MainActor.run {
self.tableView.reloadData()
}
Apesar de todas as suas vantagens, @MainActor tem uma série de limitações que são importantes considerar ao projetar a arquitetura da aplicação. Compreender os limites de aplicabilidade ajuda a evitar o uso incorreto.
Se toda a cadeia de chamadas estiver marcada com @MainActor, então qualquer trabalho pesado será executado na thread principal, causando congelamentos da UI. Recomenda-se marcar apenas a camada de UI com @MainActor, deixando a lógica de negócios e requisições de rede em atores secundários ou no executor global.
APIs antigas baseadas em callbacks (por exemplo, URLSession sem async/await) não suportam o contexto do ator. A integração requer um wrapper com CheckedContinuation. Além disso, @MainActor não é compatível com performSelector, target-action e outros padrões não assíncronos do UIKit.
Ao depurar aplicações com @MainActor, é mais difícil reproduzir condições de corrida porque o compilador previne muitas delas em tempo de compilação em vez de em tempo de execução. No entanto, isso pode criar uma falsa sensação de segurança: o trabalho incorreto com objetos mutáveis compartilhados (por exemplo, NSCache ou variáveis globais compartilhadas) ainda é possível se não estiverem marcados com @MainActor e forem usados sem sincronização explícita.
@MainActor simplifica significativamente o teste de lógica de UI, pois elimina a necessidade de trocar threads manualmente nos testes. No entanto, há particularidades que precisam ser consideradas ao escrever testes unitários e testes de UI.
No XCTest, o ambiente de teste configura automaticamente o executor da thread principal. Quando um método de teste executa na thread principal, chamar funções @MainActor não requer configuração adicional — elas executam no mesmo contexto. Para testar cenários em segundo plano, use MainActor.run dentro de uma Task com prioridade e executor explícitos, verificando separadamente se o código funciona corretamente quando chamado do segundo plano.
Uma abordagem comum é testar ViewModel com @MainActor, onde se verifica que as propriedades @Published atualizam corretamente após operações assíncronas. Graças à herança de contexto do ator, chamar await dentro do teste garante execução na thread principal sem garantias adicionais de DispatchQueue ou troca manual de contexto, o que simplifica a escrita de testes.
Ao refatorar código existente para Swift Concurrency, verifique o isolamento de @MainActor através do compilador: qualquer chamada a métodos síncronos sem @MainActor a partir de um contexto @MainActor é marcada como erro. Esta propriedade é usada para migrar gradualmente um projeto para async/await: você marca a camada ViewModel como @MainActor, e o compilador destaca todas as chamadas inseguras que precisam ser movidas para atores secundários.
Ao criar mocks para dependências de @MainActor, use protocolos com métodos async que declaram funções assíncronas com tipos de retorno. Isso permite substituir serviços de rede, bancos de dados e outras dependências externas sem quebrar o isolamento do ator. O compilador verifica que o mock implementa todos os requisitos de isolamento, prevenindo acesso acidental a código @MainActor a partir de threads de teste em segundo plano.
Ao testar sincronamente código @MainActor, use XCTestExpectation para aguardar a conclusão de operações assíncronas. Defina a expectativa no teste e chame fulfillment dentro de um fechamento que executa na thread principal. Se o teste travar indefinidamente — provavelmente a chamada na thread principal não está ocorrendo, e você precisa verificar o isolamento do ator. Para depurar o contexto de execução, é útil adicionar uma verificação Thread.isMainThread dentro do código de teste.
Perguntas Frequentes
Não, é suficiente marcar apenas os métodos que atualizam a UI. No entanto, se uma classe tiver vários desses métodos, é mais simples adicionar @MainActor a toda a classe. Isso garante que todos os seus membros executem na thread principal e simplifica a manutenção do código.
@MainActor é uma instância específica de um ator global vinculada à thread principal. @globalActor é um protocolo para criar seus próprios atores globais. Por exemplo, você pode criar um @BackgroundActor para executar código em uma thread secundária se a arquitetura do projeto exigir.
Sim, funções síncronas com @MainActor também executam na thread principal. No entanto, o principal valor do @MainActor é revelado com async/await, quando uma função assíncrona retoma automaticamente na thread principal sem troca manual via DispatchQueue.main.
Task.cancel() funciona com tarefas @MainActor da mesma forma que com tarefas normais. Uma tarefa @MainActor pode verificar Task.isCancelled ou lançar CancellationError. Ao cancelar, a thread principal não é bloqueada — a tarefa simplesmente para a execução no ponto de suspensão mais próximo.
O compilador garante segurança: se você chamar uma função @MainActor de um contexto secundário, o compilador indicará o erro. Para chamadas assíncronas, basta marcar o código chamador com await, e o executor trocará para a thread principal. Para chamadas síncronas, é necessária troca explícita via MainActor.run.
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