Swift Macros: exemplos práticos de metaprogramação

Guia completo sobre Swift Macros: criação de macros freestanding e attached incluindo body macros do Swift 6, manipulação da AST com swift-syntax e exemplos práticos para eliminar código repetitivo.

Metaprogramação com Swift Macros e exemplos práticos de código

As Swift Macros transformam a forma como se escreve e mantém o código. Introduzidas com o Swift 5.9 e ampliadas significativamente no Swift 6, as macros permitem gerar código em tempo de compilação sem abrir mão da segurança de tipos estática. Diferentemente das macros do pré-processador C, as Swift Macros são seguras em tipos, integradas ao compilador e totalmente suportadas pelas ferramentas de desenvolvimento, incluindo o recurso "Expand Macro" do Xcode.

O que este guia aborda

Este guia cobre a criação de Swift Macros desde os conceitos fundamentais até implementações avançadas, incluindo as novas body macros introduzidas no Swift 6 (SE-0415). Todos os exemplos usam Swift 6 e swift-syntax 600.0.0+.

Entendendo os tipos de Swift Macros

O Swift oferece duas categorias principais de macros, cada uma com casos de uso distintos. As macros freestanding atuam de forma autônoma como expressões ou declarações, enquanto as macros attached se associam a declarações existentes para modificá-las ou enriquecê-las.

Macros freestanding: expressão e declaração

Macros freestanding começam com o símbolo # e podem retornar um valor (expressão) ou criar novas declarações. A seguir, um exemplo concreto de macro de expressão:

MacroUsage.swiftswift
// Freestanding expression macro - generates a value
let buildInfo = #buildDate
// Expansion → "2026-08-23 10:30:45"

// Freestanding macro with arguments
let message = #stringify(1 + 2)
// Expansion → "1 + 2 = 3"

// Freestanding declaration macro - creates declarations
#makeCase("success", "failure", "pending")
// Expansion →
// case success
// case failure
// case pending

A diferença essencial entre expressão e declaração está no resultado: uma expressão produz um valor, ao passo que uma declaração produz código estrutural (tipos, funções, variáveis).

Macros attached: os seis papéis

Macros attached começam com @ e ficam à frente de uma declaração. O Swift 6 define seis papéis distintos para essas macros, incluindo o novo papel body para implementação de funções:

AttachedMacroRoles.swiftswift
// @attached(peer) - adds declarations at the same level
@AddAsync
func fetchUser(id: Int) -> User { ... }
// Expansion → adds func fetchUserAsync(id: Int) async -> User

// @attached(accessor) - adds getters/setters
@UserDefault("theme")
var currentTheme: String
// Expansion → adds get { UserDefaults.standard.string(...) }

// @attached(member) - adds members to a type
@AutoEquatable
struct Point {
    var x: Int
    var y: Int
}
// Expansion → adds static func == (lhs: Point, rhs: Point) -> Bool

// @attached(memberAttribute) - applies attributes to members
@CodableKeys
struct Config {
    var apiUrl: String
    var timeout: Int
}
// Expansion → adds @CodingKey("api_url") before apiUrl

// @attached(extension) - adds protocol conformances
@Hashable
struct User {
    var id: Int
    var name: String
}
// Expansion → adds extension User: Hashable { ... }

// @attached(body) - NEW in Swift 6: synthesizes function bodies
@Remote
func fetchData(endpoint: String) -> Data
// Expansion → generates the entire function implementation

Esses papéis podem ser combinados para criar macros poderosas, capazes de transformar o código em várias dimensões ao mesmo tempo.

Swift 6 Body Macros: SE-0415

O Swift 6 introduziu as function body macros por meio da SE-0415, permitindo que macros sintetizem ou substituam implementações de funções. Isso cobre casos de uso que nenhum papel anterior conseguia tratar.

Protocolo BodyMacro

O protocolo BodyMacro permite a substituição completa do corpo de uma função:

BodyMacro.swiftswift
import SwiftSyntax
import SwiftSyntaxMacros

public protocol BodyMacro: AttachedMacro {
    static func expansion(
        of node: AttributeSyntax,
        providingBodyFor declaration: some DeclSyntaxProtocol &
            WithOptionalCodeBlockSyntax,
        in context: some MacroExpansionContext
    ) throws -> [CodeBlockItemSyntax]
}

A macro recebe a declaração original e retorna itens de bloco de código que se tornam o corpo da função. Qualquer corpo existente é substituído.

Implementando uma macro de chamada de procedimento remoto

Body macros são particularmente úteis para gerar código de camada de rede a partir de assinaturas de função:

RemoteMacro.swiftswift
import SwiftSyntax
import SwiftSyntaxMacros

public struct RemoteMacro: BodyMacro {
    public static func expansion(
        of node: AttributeSyntax,
        providingBodyFor declaration: some DeclSyntaxProtocol &
            WithOptionalCodeBlockSyntax,
        in context: some MacroExpansionContext
    ) throws -> [CodeBlockItemSyntax] {
        guard let funcDecl = declaration.as(FunctionDeclSyntax.self) else {
            throw MacroError.invalidSyntax("@Remote requires a function")
        }
        
        let functionName = funcDecl.name.text
        let parameters = funcDecl.signature.parameterClause.parameters
        
        // Generate parameter encoding
        let paramDict = parameters.map { param in
            let name = param.firstName.text
            return "\"\(name)\": \(name)"
        }.joined(separator: ", ")
        
        // Generate the RPC body
        return [
            """
            let params: [String: Any] = [\(raw: paramDict)]
            return try await RPCClient.shared.call(
                method: "\(raw: functionName)",
                params: params
            )
            """
        ]
    }
}

Declaração e uso

MyMacros.swiftswift
/// Generates a remote procedure call implementation
@attached(body)
public macro Remote() = #externalMacro(
    module: "MyMacrosPlugin",
    type: "RemoteMacro"
)
APIService.swiftswift
class APIService {
    @Remote
    func createUser(name: String, email: String) async throws -> User
    
    @Remote
    func deleteUser(id: UUID) async throws -> Bool
    
    // Each function body is generated at compile time:
    // func createUser(name: String, email: String) async throws -> User {
    //     let params: [String: Any] = ["name": name, "email": email]
    //     return try await RPCClient.shared.call(
    //         method: "createUser",
    //         params: params
    //     )
    // }
}
Limitação de body macro

Apenas uma body macro pode ser aplicada a uma determinada função. Para injeção de preâmbulo composível (logging, tracing), o protocolo PreambleMacro está em consideração para futuras versões do Swift.

Configuração do projeto para criar macros

A criação de Swift Macros exige um Swift Package com uma estrutura específica. O pacote depende do swift-syntax, biblioteca oficial para manipular código Swift como uma árvore de sintaxe abstrata (AST).

Estrutura do Package.swift

Package.swiftswift
// swift-tools-version: 6.0
import PackageDescription

let package = Package(
    name: "MyMacros",
    platforms: [.macOS(.v10_15), .iOS(.v13)],
    products: [
        // Library exposing macros to the main project
        .library(
            name: "MyMacros",
            targets: ["MyMacros"]
        ),
        // Executable for testing macros
        .executable(
            name: "MyMacrosClient",
            targets: ["MyMacrosClient"]
        )
    ],
    dependencies: [
        // Required dependency for macros - use 600.0.0+ for Swift 6
        .package(
            url: "https://github.com/swiftlang/swift-syntax.git",
            from: "600.0.0"
        )
    ],
    targets: [
        // Compiler plugin containing implementation
        .macro(
            name: "MyMacrosPlugin",
            dependencies: [
                .product(name: "SwiftSyntax", package: "swift-syntax"),
                .product(name: "SwiftSyntaxMacros", package: "swift-syntax"),
                .product(name: "SwiftCompilerPlugin", package: "swift-syntax")
            ]
        ),
        // Target exposing macro declarations
        .target(
            name: "MyMacros",
            dependencies: ["MyMacrosPlugin"]
        ),
        // Test client
        .executableTarget(
            name: "MyMacrosClient",
            dependencies: ["MyMacros"]
        ),
        // Unit tests
        .testTarget(
            name: "MyMacrosTests",
            dependencies: [
                "MyMacrosPlugin",
                .product(name: "SwiftSyntaxMacrosTestSupport", package: "swift-syntax")
            ]
        )
    ]
)

Essa configuração separa de forma clara as declarações das macros (o que o código cliente enxerga) de sua implementação (executada em tempo de compilação).

Organização recomendada

São necessários no mínimo três arquivos: MyMacros.swift para as declarações, MyMacrosPlugin.swift para as implementações e MyMacrosTests.swift para os testes. Essa separação facilita a manutenção.

Criando uma macro de expressão

Macros de expressão geram um valor utilizável diretamente no código. Veja como criar uma macro #unwrap que faz o unwrap de um opcional com uma mensagem de erro personalizada que inclui o nome da variável. Para mais informações sobre padrões de tratamento de erros em Swift, consulte o guia sobre concorrência estruturada em Swift.

Declaração da macro

MyMacros.swiftswift
import Foundation

/// Macro that unwraps an optional with an explicit error message
/// Usage: let value = #unwrap(optionalValue)
/// Expansion: guard let optionalValue else { fatalError("...") }; optionalValue
@freestanding(expression)
public macro unwrap<T>(_ value: T?) -> T = #externalMacro(
    module: "MyMacrosPlugin",
    type: "UnwrapMacro"
)

A assinatura indica que a macro recebe um opcional e devolve o valor não opcional. O #externalMacro aponta para a implementação dentro do plugin.

Implementação com swift-syntax

UnwrapMacro.swiftswift
import SwiftSyntax
import SwiftSyntaxMacros
import SwiftCompilerPlugin

public struct UnwrapMacro: ExpressionMacro {
    public static func expansion(
        of node: some FreestandingMacroExpansionSyntax,
        in context: some MacroExpansionContext
    ) throws -> ExprSyntax {
        // Get the first argument passed to the macro
        guard let argument = node.argumentList.first?.expression else {
            throw MacroError.missingArgument
        }

        // Extract the variable name for the error message
        let variableName = argument.description.trimmingCharacters(
            in: .whitespacesAndNewlines
        )

        // Generate the expansion code
        // Uses an immediately-invoked closure to encapsulate the guard
        return """
            {
                guard let value = \(argument) else {
                    fatalError("Failed to unwrap '\\(\(literal: variableName))' - value was nil")
                }
                return value
            }()
            """
    }
}

// Custom errors for macros
enum MacroError: Error, CustomStringConvertible {
    case missingArgument
    case invalidSyntax(String)

    var description: String {
        switch self {
        case .missingArgument:
            return "The macro requires an argument"
        case .invalidSyntax(let message):
            return "Invalid syntax: \(message)"
        }
    }
}

O método expansion recebe o nó da AST que representa a chamada da macro e o contexto de compilação. Ele retorna um ExprSyntax que contém o código gerado.

Registro do plugin

MyMacrosPlugin.swiftswift
import SwiftCompilerPlugin
import SwiftSyntaxMacros

@main
struct MyMacrosPlugin: CompilerPlugin {
    // List all macros provided by this plugin
    let providingMacros: [Macro.Type] = [
        UnwrapMacro.self,
        RemoteMacro.self,
        AutoInitMacro.self,
        // Add other macros here
    ]
}

Esse ponto de entrada informa ao compilador as macros disponíveis no plugin.

Pronto para mandar bem nas entrevistas de iOS?

Pratique com nossos simuladores interativos, flashcards e testes tecnicos.

Criando uma macro attached do tipo member

Macros member adicionam membros (propriedades, métodos, tipos aninhados) a um tipo existente. A seguir, uma macro @AutoInit que gera automaticamente um inicializador com todas as propriedades armazenadas.

Declaração e implementação completas

MyMacros.swiftswift
/// Automatically generates an initializer with all stored properties
@attached(member, names: named(init))
public macro AutoInit() = #externalMacro(
    module: "MyMacrosPlugin",
    type: "AutoInitMacro"
)
AutoInitMacro.swiftswift
import SwiftSyntax
import SwiftSyntaxMacros

public struct AutoInitMacro: MemberMacro {
    public static func expansion(
        of node: AttributeSyntax,
        providingMembersOf declaration: some DeclGroupSyntax,
        in context: some MacroExpansionContext
    ) throws -> [DeclSyntax] {
        // Verify the macro is applied to a struct or class
        guard declaration.is(StructDeclSyntax.self) ||
              declaration.is(ClassDeclSyntax.self) else {
            throw MacroError.invalidSyntax(
                "@AutoInit can only be applied to structs and classes"
            )
        }

        // Collect stored properties
        let properties = declaration.memberBlock.members
            .compactMap { $0.decl.as(VariableDeclSyntax.self) }
            .filter { isStoredProperty($0) }

        // Generate initializer parameters
        let parameters = properties.compactMap { property -> String? in
            guard let binding = property.bindings.first,
                  let identifier = binding.pattern.as(IdentifierPatternSyntax.self),
                  let type = binding.typeAnnotation?.type else {
                return nil
            }

            let name = identifier.identifier.text
            let typeName = type.description.trimmingCharacters(in: .whitespaces)

            // Check if the property has a default value
            if binding.initializer != nil {
                return "\(name): \(typeName) = \(binding.initializer!.value)"
            }
            return "\(name): \(typeName)"
        }

        // Generate assignments in the init body
        let assignments = properties.compactMap { property -> String? in
            guard let binding = property.bindings.first,
                  let identifier = binding.pattern.as(IdentifierPatternSyntax.self) else {
                return nil
            }
            let name = identifier.identifier.text
            return "self.\(name) = \(name)"
        }

        // Build the complete initializer
        let initDecl: DeclSyntax = """
            public init(\(raw: parameters.joined(separator: ", "))) {
                \(raw: assignments.joined(separator: "\n        "))
            }
            """

        return [initDecl]
    }

    // Check if a variable is a stored property (not computed)
    private static func isStoredProperty(_ variable: VariableDeclSyntax) -> Bool {
        guard let binding = variable.bindings.first else { return false }

        // A computed property has an accessor block with get/set
        if let accessor = binding.accessorBlock {
            // If it's a block with explicit accessors, it's computed
            if accessor.accessors.is(AccessorDeclListSyntax.self) {
                return false
            }
        }

        // let or var without accessor = stored property
        return true
    }
}

Uso da macro AutoInit

UserModel.swiftswift
@AutoInit
struct User {
    let id: UUID
    var name: String
    var email: String
    var isActive: Bool = true
}

// Automatically generated code:
// public init(id: UUID, name: String, email: String, isActive: Bool = true) {
//     self.id = id
//     self.name = name
//     self.email = email
//     self.isActive = isActive
// }

// Usage
let user = User(id: UUID(), name: "Alice", email: "alice@example.com")
// isActive uses the default value

Essa macro elimina o boilerplate do inicializador, algo especialmente útil em modelos de dados com muitas propriedades.

Macro attached peer para geração async

Macros peer adicionam declarações no mesmo nível da declaração anotada. Veja a macro @AddAsync que gera a versão async de uma função baseada em completion handler. Esse padrão é especialmente relevante ao migrar código de rede legado, conforme abordado no guia de migração Combine vs async/await.

MyMacros.swiftswift
/// Automatically generates an async version of a function with completion handler
@attached(peer, names: suffixed(Async))
public macro AddAsync() = #externalMacro(
    module: "MyMacrosPlugin",
    type: "AddAsyncMacro"
)
AddAsyncMacro.swiftswift
import SwiftSyntax
import SwiftSyntaxMacros

public struct AddAsyncMacro: PeerMacro {
    public static func expansion(
        of node: AttributeSyntax,
        providingPeersOf declaration: some DeclSyntax,
        in context: some MacroExpansionContext
    ) throws -> [DeclSyntax] {
        // Verify it's a function
        guard let funcDecl = declaration.as(FunctionDeclSyntax.self) else {
            throw MacroError.invalidSyntax(
                "@AddAsync requires a function"
            )
        }

        let functionName = funcDecl.name.text
        let asyncFunctionName = "\(functionName)Async"

        // Analyze parameters to find the completion handler
        let parameters = funcDecl.signature.parameterClause.parameters

        // Filter parameters (exclude completion handler)
        var regularParams: [String] = []
        var completionType: String? = nil

        for param in parameters {
            let paramType = param.type.description

            // Detect a completion handler (closure with Result or simple value)
            if paramType.contains("->") && paramType.contains("Void") {
                // Extract the return type from completion
                completionType = extractCompletionReturnType(from: paramType)
            } else {
                let paramName = param.firstName.text
                let paramSecondName = param.secondName?.text
                let label = paramSecondName ?? paramName
                regularParams.append("\(paramName): \(paramType)")
            }
        }

        guard let returnType = completionType else {
            throw MacroError.invalidSyntax(
                "No completion handler found"
            )
        }

        // Generate arguments for internal call
        let callArgs = parameters.dropLast().map { param in
            let name = param.firstName.text
            return "\(name): \(name)"
        }.joined(separator: ", ")

        // Generate the async function
        let asyncFunc: DeclSyntax = """
            func \(raw: asyncFunctionName)(\(raw: regularParams.joined(separator: ", "))) async throws -> \(raw: returnType) {
                try await withCheckedThrowingContinuation { continuation in
                    \(raw: functionName)(\(raw: callArgs.isEmpty ? "" : callArgs + ", ")completion: { result in
                        switch result {
                        case .success(let value):
                            continuation.resume(returning: value)
                        case .failure(let error):
                            continuation.resume(throwing: error)
                        }
                    })
                }
            }
            """

        return [asyncFunc]
    }

    // Extract return type from a Result type
    private static func extractCompletionReturnType(from type: String) -> String {
        // Simplified pattern - in production, use the AST
        if let match = type.range(of: #"Result<([^,]+)"#, options: .regularExpression) {
            var result = String(type[match])
            result = result.replacingOccurrences(of: "Result<", with: "")
            return result.trimmingCharacters(in: .whitespaces)
        }
        return "Void"
    }
}

Demonstração da macro AddAsync

NetworkService.swiftswift
class NetworkService {
    @AddAsync
    func fetchUser(
        id: Int,
        completion: @escaping (Result<User, Error>) -> Void
    ) {
        // Implementation with callback
        URLSession.shared.dataTask(with: URL(string: "/users/\(id)")!) { data, _, error in
            if let error = error {
                completion(.failure(error))
            } else if let data = data {
                let user = try? JSONDecoder().decode(User.self, from: data)
                completion(.success(user!))
            }
        }.resume()
    }

    // Automatically generates:
    // func fetchUserAsync(id: Int) async throws -> User {
    //     try await withCheckedThrowingContinuation { continuation in
    //         fetchUser(id: id, completion: { result in
    //             switch result {
    //             case .success(let value):
    //                 continuation.resume(returning: value)
    //             case .failure(let error):
    //                 continuation.resume(throwing: error)
    //             }
    //         })
    //     }
    // }
}

// Modern usage with async/await
let user = try await networkService.fetchUserAsync(id: 42)
Limitações das macros peer

O nome da função gerada precisa ser declarado em names: do atributo @attached. Aqui, suffixed(Async) indica que a função gerada vai ter o sufixo "Async" anexado ao nome original.

Testes unitários para macros

Testar macros é essencial, já que elas geram código que será compilado. O Swift fornece SwiftSyntaxMacrosTestSupport para facilitar esses testes. A partir do swift-syntax 603.0.0+, o Swift Testing também é suportado junto com o XCTest.

MyMacrosTests.swiftswift
import SwiftSyntaxMacros
import SwiftSyntaxMacrosTestSupport
import XCTest
@testable import MyMacrosPlugin

final class MyMacrosTests: XCTestCase {

    // Dictionary of macros to test
    let testMacros: [String: Macro.Type] = [
        "unwrap": UnwrapMacro.self,
        "AutoInit": AutoInitMacro.self,
        "AddAsync": AddAsyncMacro.self,
        "Remote": RemoteMacro.self
    ]

    func testUnwrapMacroExpansion() throws {
        assertMacroExpansion(
            """
            let value = #unwrap(optionalString)
            """,
            expandedSource: """
            let value = {
                guard let value = optionalString else {
                    fatalError("Failed to unwrap 'optionalString' - value was nil")
                }
                return value
            }()
            """,
            macros: testMacros
        )
    }

    func testAutoInitMacroWithStruct() throws {
        assertMacroExpansion(
            """
            @AutoInit
            struct Point {
                let x: Int
                var y: Int
            }
            """,
            expandedSource: """
            struct Point {
                let x: Int
                var y: Int

                public init(x: Int, y: Int) {
                    self.x = x
                    self.y = y
                }
            }
            """,
            macros: testMacros
        )
    }

    func testAutoInitWithDefaultValues() throws {
        assertMacroExpansion(
            """
            @AutoInit
            struct Config {
                var timeout: Int = 30
                var retryCount: Int
            }
            """,
            expandedSource: """
            struct Config {
                var timeout: Int = 30
                var retryCount: Int

                public init(timeout: Int = 30, retryCount: Int) {
                    self.timeout = timeout
                    self.retryCount = retryCount
                }
            }
            """,
            macros: testMacros
        )
    }

    func testAutoInitFailsOnEnum() throws {
        assertMacroExpansion(
            """
            @AutoInit
            enum Status {
                case active
            }
            """,
            expandedSource: """
            enum Status {
                case active
            }
            """,
            diagnostics: [
                DiagnosticSpec(
                    message: "@AutoInit can only be applied to structs and classes",
                    line: 1,
                    column: 1
                )
            ],
            macros: testMacros
        )
    }

    func testRemoteMacroExpansion() throws {
        assertMacroExpansion(
            """
            @Remote
            func fetchUser(id: Int) async throws -> User
            """,
            expandedSource: """
            func fetchUser(id: Int) async throws -> User {
                let params: [String: Any] = ["id": id]
                return try await RPCClient.shared.call(
                    method: "fetchUser",
                    params: params
                )
            }
            """,
            macros: testMacros
        )
    }
}

Os testes verificam a expansão correta do código e as mensagens de erro adequadas para usos inválidos.

Depurar e inspecionar macros

O Xcode oferece várias ferramentas para depurar macros e entender o código gerado.

Expansão no Xcode

DebuggingMacros.swiftswift
// Right-click on macro call → "Expand Macro"
// Displays generated code inline

@AutoInit
struct Product {
    let id: UUID
    var name: String
    var price: Decimal
}

// To see the expansion:
// 1. Right-click on @AutoInit
// 2. Select "Expand Macro"
// 3. Generated code displays inline for inspection and debugging

Logging durante o desenvolvimento

DebugMacro.swiftswift
public struct DebugMacro: ExpressionMacro {
    public static func expansion(
        of node: some FreestandingMacroExpansionSyntax,
        in context: some MacroExpansionContext
    ) throws -> ExprSyntax {
        // Print the node's AST to understand the structure
        print("=== DEBUG MACRO ===")
        print("Node: \(node)")
        print("Arguments: \(node.argumentList)")

        // Complete dump of the syntax tree
        dump(node)

        // Continue with normal expansion
        return "42"
    }
}

Explorando a AST com o swift-ast-explorer

A ferramenta on-line swift-ast-explorer.com permite visualizar a árvore de sintaxe de qualquer código Swift. Compreender a estrutura dos nós da AST é essencial ao implementar macros, conforme demonstrado no guia do framework Swift Testing, que aborda as macros de teste integradas ao Swift.

Boas práticas para Swift Macros

Criar macros sustentáveis exige seguir certas convenções e evitar armadilhas comuns.

Validação e mensagens de erro

ValidationBestPractices.swiftswift
public struct ValidatedMacro: MemberMacro {
    public static func expansion(
        of node: AttributeSyntax,
        providingMembersOf declaration: some DeclGroupSyntax,
        in context: some MacroExpansionContext
    ) throws -> [DeclSyntax] {
        // Validate usage context
        guard declaration.is(StructDeclSyntax.self) else {
            // Clear error messages with possible localization
            context.diagnose(
                Diagnostic(
                    node: node,
                    message: MacroDiagnosticMessage(
                        id: "invalid-target",
                        message: "This macro can only be applied to structs",
                        severity: .error
                    )
                )
            )
            return []
        }

        // Check required arguments
        guard let arguments = node.arguments else {
            context.diagnose(
                Diagnostic(
                    node: node,
                    message: MacroDiagnosticMessage(
                        id: "missing-args",
                        message: "Required arguments missing",
                        severity: .error
                    )
                )
            )
            return []
        }

        // Implementation...
        return []
    }
}

// Structure for diagnostic messages
struct MacroDiagnosticMessage: DiagnosticMessage {
    let id: String
    let message: String
    let severity: DiagnosticSeverity

    var diagnosticID: MessageID {
        MessageID(domain: "MyMacros", id: id)
    }
}

Gerar código legível

ReadableCodeGeneration.swiftswift
// Avoid: hard-to-read generated code
let badCode: DeclSyntax = "public init(a:Int,b:String,c:Bool){self.a=a;self.b=b;self.c=c}"

// Preferred: properly formatted generated code
let goodCode: DeclSyntax = """
    public init(
        a: Int,
        b: String,
        c: Bool
    ) {
        self.a = a
        self.b = b
        self.c = c
    }
    """

O código gerado precisa ser tão legível quanto o escrito à mão, já que quem desenvolve vai inspecioná-lo via "Expand Macro".

Sources

O que as Swift Macros permitem na prática

As Swift Macros representam uma ferramenta poderosa para eliminar boilerplate sem abrir mão da segurança de tipos estática. Essa tecnologia permite:

  • Duas categorias de macros: freestanding (#) e attached (@)
  • Seis papéis attached no Swift 6: peer, accessor, member, memberAttribute, extension, e o novo papel body
  • Implementação via swift-syntax 600.0.0+ e manipulação da AST
  • Testes obrigatórios com SwiftSyntaxMacrosTestSupport, agora com suporte ao Swift Testing
  • Pacote separado obrigatório para as implementações
  • Depuração via "Expand Macro" no Xcode
  • Mensagens de erro explícitas, essenciais para a experiência de quem desenvolve

As Swift Macros são particularmente úteis para gerar conformances (Equatable, Codable), criar property wrappers avançados, sintetizar implementações RPC a partir de assinaturas, e modernizar APIs baseadas em callbacks rumo ao async/await.

Comece a praticar!

Teste seus conhecimentos com nossos simuladores de entrevista e testes tecnicos.

Desafio do dia

Você saberia encontrar o bug em iOS?

Um trecho real, um bug escondido, uma tentativa por dia. Sem conta para testar.

Anthony Fillion-Maillet

Escrito por

Anthony Fillion-Maillet

Fundador da SharpSkill

Desenvolvedor fullstack há mais de 10 anos. Dirige a SharpSkill e responde por tudo o que é publicado aqui.

Atualizado em 23 de agosto de 2026

Tags

#swift
#ios
#macros
#metaprogramming
#swift-syntax

Compartilhar

Artigos relacionados