@MainActor — o que é, aplicação e características no código assíncrono Swift

Autor: IT Sectr Publicado: 2026-03-19 Tempo de leitura: 8 min

@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 do Swift para garantir execução na thread principal.
  • O sistema async/await — a base sobre a qual @MainActor é construído.
  • Anotação de classe coloca automaticamente todos os seus métodos na thread principal.
  • Ao contrário de DispatchQueue.main, @MainActor verifica a thread no nível do compilador.
  • Atualizações de UI — a principal área de aplicação de @MainActor no desenvolvimento iOS.

O que é @MainActor?

@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.

Definição e lugar no Swift Concurrency

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.

Razões para a criação

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.

Como funciona @MainActor?

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.

Executor da thread principal

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.

swift
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
    }
}

Herança de contexto do ator

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.

@MainActor vs DispatchQueue.main

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.

Segurança no nível de tipos

@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.

Desempenho e sobrecarga

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.

swift
// Abordagem antiga
DispatchQueue.main.async {
    self.updateUI()
}

// Nova abordagem com @MainActor
@MainActor
func updateUI() {
    // executa na thread principal
    self.label.text = "Atualizado"
}
Critério@MainActorDispatchQueue.main
Verificaçãocompiladorruntime
Sintaxeanotação (declarativa)chamada (imperativa)
Sobrecargabaixa (troca de executor)média (fechamento + fila)
Testabilidadealta (MainActor.shared pode ser substituído)baixa (difícil de mockar)

Uso de @MainActor em projetos iOS

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.

Anotação de classe ou estrutura

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.

swift
@MainActor
final class UserListViewModel: ObservableObject {
    @Published var users: [User] = []
    @Published var isLoading = false

    func fetchUsers() async {
        isLoading = true
        users = await api.getUsers()
        isLoading = false
    }
}

Encapsulando código legado

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.

swift
await MainActor.run {
    self.tableView.reloadData()
}

Limitações do @MainActor

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.

Desempenho sob uso intensivo

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.

Incompatibilidade com algumas APIs

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.

Depuração multithread

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.

Testando @MainActor

@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.

Testes unitários com MainActor

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.

Verificando isolamento durante refatoração

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.

Mocking e contexto de Ator

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.

Espera por operações assíncronas

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

É necessário marcar a classe inteira com @MainActor?

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.

Como @MainActor difere de @globalActor?

@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.

Pode-se usar @MainActor sem async/await?

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.

Como cancelar uma tarefa @MainActor?

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 que acontece se @MainActor for chamado de uma thread secundária?

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

  • @MainActor — um ator global do Swift que garante execução na thread principal.
  • Verificação do compilador elimina toda uma classe de erros de segurança de UI.
  • Anotação de classe completa coloca automaticamente todos os seus métodos na thread principal.
  • MainActor.run — troca explícita para código legado e contextos síncronos.
  • Ao contrário de DispatchQueue.main, @MainActor não cria fechamentos e usa troca de executor.
  • Cálculos pesados não devem ser executados sob @MainActor para evitar congelamentos de UI.
  • Herança de contexto do ator simplifica cadeias de chamadas assíncronas e torna o código consistente, previsível e seguro para a UI.

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.

Discutir o projeto

Leia também