No fim da parte sobre property wrappers, deixamos uma pista: “nem tudo que tem arroba é wrapper”. @Observable é o protagonista.
Este conteúdo continua a partir do artigo anterior Swift avançado #6.
Parece um wrapper, mas na verdade é um macro. Ao usar #Preview no Xcode ou @Model do SwiftData, já somos consumidores de macros.
Nesta edição da série avançada, resumimos o que são os macros introduzidos no Swift 5.9, quais problemas resolvem e que custo cobram. SE-0382: Expression Macros
O problema que os macros resolvem — o último bastião do código repetitivo
O Swift vem adicionando mecanismos para reduzir código repetitivo: implementações padrão de protocolos, síntese automática de Codable e property wrappers.
Mas restava uma área intocada por essas ferramentas: ler a estrutura de um tipo e gerar código de acordo com ela.
Observation é um bom exemplo. Para avisar os observadores quando uma propriedade muda, é necessário código de rastreamento para cada propriedade armazenada.
Com um wrapper(@Published), era preciso colocar uma arroba em cada propriedade; as esquecidas simplesmente ficavam fora da observação.
O necessário é: “gere código adequado a cada nome para todas as propriedades armazenadas desta classe”. Isso exige ler a estrutura do tipo, algo fora das capacidades de um wrapper.
Tradicionalmente, duas opções cuidavam dessa área: as técnicas dinâmicas do runtime do Objective-C (custo em tempo de execução e falta de transparência), ou ferramentas externas de geração como o Sourcery (gerenciamento separado, fora do pipeline de build).
Os macros trazem esse trabalho para dentro da linguagem e, além disso, para o tempo de compilação.
Como funciona — plugin de geração de código conectado ao compilador
A essência dos macros do Swift é ser um plugin do compilador. Isso é fundamentalmente diferente da substituição de texto do pré-processador de C. A sequência é esta.
- Quando encontra um uso de macro (# ou @) no código-fonte, o compilador passa a árvore sintática (AST) desse código para a implementação do macro.
- A implementação do macro é um programa Swift executado em um processo separado. Ele analisa a árvore com a biblioteca SwiftSyntax e devolve novos trechos de código.
- O compilador incorpora o resultado no local original e depois segue com uma compilação normal.
Dessa estrutura surgem propriedades importantes.
Primeiro, o resultado é código Swift real e inspecionável. No Xcode, clique com o botão direito no macro e selecione “Expand Macro” para ver o código gerado diretamente.
Poder abrir a qualquer momento a cortina da magia é a versão dos macros de “ocultar, mas não esconder”, vista na parte sobre Progressive Disclosure.
Segundo, ele é higiênico. As variáveis criadas pelo macro são gerenciadas para não colidir com nomes do código ao redor, e o macro não pode alterar código fora do escopo da função declarada.
Os famosos efeitos colaterais dos macros de C são bloqueados ainda na fase de projeto.
Terceiro, ele é isolado em um sandbox. O processo do macro não pode acessar arquivos nem a rede, evitando que se torne uma brecha de segurança para executar código arbitrário durante a compilação.
Duas ramificações — freestanding(#) e attached(@)
Os macros se dividem em dois grupos conforme a forma como são anexados.
O macro freestanding(#) gera código naquele local. #Preview { MyView() }cria a estrutura de registro da visualização prévia.
Macros do tipo #URL("https://apple.com") validam strings em tempo de compilação e criam uma URL desembrulhada. São macros que ocupam o lugar de uma expressão.
O macro attached(@) expande a declaração à qual está anexado. Seus papéis são subdivididos.
Há member, que adiciona membros; accessor, que acrescenta acessores às propriedades; e extension, que adiciona conformidade a protocolos. Um único macro pode exercer vários papéis.
@Observable é exatamente essa combinação. Ele adiciona acessores de rastreamento a cada propriedade armazenada da classe (accessor) e membros do registro de observação (member). Além disso, adiciona a conformidade ao protocolo Observable (extension).
É por isso que o incômodo da época do @Published, que exigia uma arroba por propriedade, foi reduzido a uma única arroba no tipo.
Com essa distinção, a documentação das bibliotecas fica mais clara. Pense no @Model do SwiftData, no #expect do framework de testes (o macro visto na parte sobre Swift Testing) e no @Reducer do Composable Architecture.
As principais APIs do ecossistema recente pertencem a uma dessas duas ramificações.
A conta — criar macros ou apenas usá-los?
Os macros têm um custo claro, e consumidores e produtores fazem contas diferentes.
Para quem usa, o custo é principalmente o tempo de build. A implementação do macro depende do SwiftSyntax, uma biblioteca grande.
Por isso, ao fazer o primeiro build de um pacote que usa macros, a compilação completa do SwiftSyntax entra no processo.
É comum um build limpo no CI passar a durar vários minutos, e a comunidade continua aprimorando soluções como a distribuição de binários pré-compilados.
Ainda assim, para o consumidor, geralmente é um custo aceitável. É possível inspecionar com Expand Macro, e não há custo em tempo de execução.
Para quem cria, o custo é muito maior. Escrever macros é “escrever código que manipula código”, então a dificuldade sobe um nível.
Isso inclui aprender a API do SwiftSyntax, criar testes específicos para macros (comparar o código de entrada com o código de saída esperado) e projetar mensagens de diagnóstico.
Por isso, na prática, é melhor ser conservador. Se o problema puder ser resolvido com implementações padrão de protocolos, genéricos ou wrappers, essas opções vêm primeiro.
Macros próprios só entram na lista quando houver, de forma ampla no código da equipe, uma “repetição que exige ler a estrutura do tipo”. É a versão macro do YAGNI (You Aren’t Gonna Need It — o princípio de não criar algo até que seja necessário).
Para a maioria das equipes, macro não é uma ferramenta para criar, mas para entender e usar corretamente o que o framework oferece.
Resumo
- Um macro é um plugin do compilador que recebe uma árvore sintática em tempo de compilação e gera código (Swift 5.9). Diferentemente da substituição de texto dos macros de C, ele lê a estrutura dos tipos e funciona de forma higiênica e isolada.
- freestanding(#) gera código no local (#Preview), enquanto attached(@) expande a declaração à qual está anexado (@Observable, @Model).
- O resultado gerado é código Swift real, inspecionável a qualquer momento com Expand Macro. Nem tudo que tem arroba é wrapper; agora, ao ler a documentação, diferencie a palavra macro.
- O custo é o tempo de build causado pelo SwiftSyntax (consumidor) e a alta dificuldade de criação (produtor). Só crie macros próprios quando for confirmada uma “repetição ampla que exige ler a estrutura do tipo”. SE-0389: Attached Macros
A próxima edição aborda conceitos que o Swift trouxe do território do Rust: os tipos não copiáveis ~Copyable e o mundo da propriedade aberto por borrowing e consuming.
Fontes e critérios de verificação
- SE-0389: Attached Macros — Swift Evolution · texto original do padrão e da especificação · verificado em 2026-08-17 · base: papéis de attached macro e declarações que podem ser geradas
- SE-0382: Expression Macros — Swift Evolution · texto original do padrão e da especificação · verificado em 2026-08-17 · base: modelo de expression macro do Swift 5.9 e plugins do compilador

![Imagem de capa de [Swift avançado #7] Macros do Swift: guia do @Observable](/assets/images/posts/2c0c42f3-4f02-48e1-8e36-2d9f4f1bf9b6/swift-macros-compiler-plugin.jpg)