Swift Macros: pratik metaprogramlama örnekleri

Swift Macros için kapsamlı rehber: freestanding ve attached makro oluşturma, Swift 6 body macros dahil, swift-syntax ile AST manipülasyonu ve tekrar eden kodu ortadan kaldıran pratik örnekler.

Swift Macros ile metaprogramlama ve pratik kod örnekleri

Swift Macros, geliştiricilerin kod yazma ve sürdürme biçimini dönüştürür. Swift 5.9 ile tanıtılan ve Swift 6'da önemli ölçüde genişletilen bu özellik, statik tip güvenliğini koruyarak derleme zamanında kod üretimini sağlar. C ön işlemcisi makrolarının aksine Swift Macros tip güvenli, derleyiciye entegre ve Xcode'un "Expand Macro" özelliği dahil geliştirme araçları tarafından tam olarak desteklenir.

Bu rehberin kapsamı

Bu rehber, Swift macro oluşturmayı temel kavramlardan ileri seviye uygulamalara kadar ele alır ve Swift 6'da tanıtılan yeni body macros'u (SE-0415) içerir. Tüm örnekler Swift 6 ve swift-syntax 600.0.0+ kullanır.

Swift Macros türlerini anlamak

Swift, her biri farklı kullanım senaryolarına hizmet eden iki ana makro kategorisi sunar. Freestanding makrolar bağımsız olarak ifade veya bildirim biçiminde çalışırken attached makrolar mevcut bildirimlere bağlanarak onları değiştirir veya zenginleştirir.

Freestanding makrolar: ifade ve bildirim

Freestanding makrolar # simgesiyle başlar ve ya bir değer döndürür (ifade) ya da yeni bildirimler oluşturur. İşte somut bir ifade makrosu örneği:

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

İfade ile bildirim arasındaki temel fark sonuçtadır: ifade bir değer üretir, bildirim ise yapısal kod üretir (tipler, fonksiyonlar, değişkenler).

Attached makrolar: altı rol

Attached makrolar @ ile başlar ve bir bildirimin önüne yerleştirilir. Swift 6, fonksiyon implementasyonu için yeni body rolü dahil bu makrolar için altı farklı rol tanımlar:

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

Bu roller, kodu aynı anda birden fazla boyutta dönüştüren güçlü makrolar oluşturmak için birleştirilebilir.

Swift 6 Body Macros: SE-0415

Swift 6, SE-0415 ile function body macros'u tanıttı ve makroların fonksiyon implementasyonlarını sentezlemesini veya değiştirmesini sağladı. Bu, daha önceki hiçbir makro rolünün karşılayamadığı kullanım senaryolarını ele alır.

BodyMacro Protokolü

BodyMacro protokolü, fonksiyon gövdesinin tamamen değiştirilmesine olanak tanır:

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

Makro orijinal bildirimi alır ve fonksiyon gövdesi haline gelen kod bloğu öğelerini döndürür. Mevcut herhangi bir gövde değiştirilir.

Remote Procedure Call Makrosu Uygulaması

Body macros, fonksiyon imzalarından ağ katmanı kodu üretmek için özellikle faydalıdır:

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
            )
            """
        ]
    }
}

Bildirim ve kullanım

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
    //     )
    // }
}
Body macro sınırlaması

Belirli bir fonksiyona en fazla bir body macro uygulanabilir. Birleştirilebilir preamble enjeksiyonu (loglama, izleme) için PreambleMacro protokolü gelecek Swift sürümlerinde değerlendirilmektedir.

Makro oluşturmak için proje kurulumu

Swift Macros oluşturmak, belirli bir yapıya sahip bir Swift Package gerektirir. Paket, Swift kodunu soyut sözdizimi ağacı (AST) olarak işleyen resmi kütüphane swift-syntax'a bağlıdır.

Package.swift yapısı

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")
            ]
        )
    ]
)

Bu yapılandırma, makro bildirimlerini (istemci kodun gördüğü bölüm) uygulamadan (derleme zamanında çalışan bölüm) net biçimde ayırır.

Önerilen düzen

En az üç dosya gereklidir: bildirimler için MyMacros.swift, uygulamalar için MyMacrosPlugin.swift ve testler için MyMacrosTests.swift. Bu ayrım bakım sürecini kolaylaştırır.

İfade makrosu oluşturma

İfade makroları kod içinde doğrudan kullanılabilir bir değer üretir. Aşağıda, opsiyonel bir değeri açan ve hata mesajına değişken adını ekleyen #unwrap makrosunun nasıl oluşturulacağı gösterilmektedir. Swift hata işleme kalıpları hakkında daha fazla bilgi için Swift structured concurrency rehberine bakılabilir.

Makronun bildirimi

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"
)

İmza, makronun bir opsiyonel aldığını ve opsiyonel olmayan değeri döndürdüğünü belirtir. #externalMacro plugin içindeki uygulamayı işaret eder.

swift-syntax ile uygulama

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)"
        }
    }
}

expansion metodu, makro çağrısını temsil eden AST düğümünü ve derleme bağlamını alır. Üretilen kodu içeren bir ExprSyntax döndürür.

Plugin kaydı

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

Bu giriş noktası, plugin içinde mevcut olan makrolar hakkında derleyiciyi bilgilendirir.

iOS mülakatlarında başarılı olmaya hazır mısın?

İnteraktif simülatörler, flashcards ve teknik testlerle pratik yap.

Attached member makrosu oluşturma

Member makroları mevcut bir tipe yeni üyeler (özellikler, metotlar, iç içe tipler) ekler. Aşağıdaki @AutoInit makrosu, tüm depolanan özellikleri içeren bir başlatıcıyı otomatik olarak üretir.

Eksiksiz bildirim ve uygulama

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

AutoInit makrosunun kullanımı

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

Bu makro başlatıcı boilerplate'ini ortadan kaldırır; bu özellikle çok sayıda alana sahip veri modellerinde değerlidir.

Async üretim için attached peer makrosu

Peer makroları, işaretlenen bildirimle aynı seviyede yeni bildirimler ekler. Aşağıda completion handler tabanlı bir fonksiyonun async sürümünü üreten @AddAsync makrosu yer alır. Bu kalıp, Combine vs async/await geçiş rehberinde ele alındığı gibi eski ağ kodunu modernize ederken özellikle önemlidir.

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

AddAsync makrosunun kullanımı

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)
Peer makrolarının sınırları

Üretilen fonksiyonun adı, @attached özniteliğinin names: bölümünde bildirilmelidir. Burada suffixed(Async), üretilen fonksiyonun orijinal adına "Async" sonekinin ekleneceğini belirtir.

Makrolar için birim testler

Makroları test etmek hayati önem taşır çünkü daha sonra derlenecek kodu üretirler. Swift, bu testleri kolaylaştırmak için SwiftSyntaxMacrosTestSupport kütüphanesini sunar. swift-syntax 603.0.0+ itibarıyla Swift Testing de XCTest'in yanı sıra desteklenmektedir.

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

Testler kodun doğru genişletilmesini ve hatalı kullanımda uygun hata mesajlarının gösterilmesini doğrular.

Makroları hata ayıklama ve inceleme

Xcode, makroların hata ayıklamasında ve üretilen kodu anlamada kullanılabilecek çeşitli araçlar sunar.

Xcode'da genişletme

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

Geliştirme sırasında günlükleme

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

swift-ast-explorer ile AST'yi keşfetmek

Çevrim içi araç swift-ast-explorer.com, herhangi bir Swift kodunun sözdizimi ağacını görselleştirmeyi sağlar. AST düğüm yapısını anlamak, Swift'e yerleşik test makrolarını ele alan Swift Testing framework rehberinde gösterildiği gibi makro uygulamada temeldir.

Swift Macros için iyi uygulamalar

Bakımı kolay makrolar oluşturmak belirli kurallara uymayı ve sık karşılaşılan tuzaklardan kaçınmayı gerektirir.

Doğrulama ve hata mesajları

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

Okunabilir kod üretmek

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

Üretilen kod, geliştiriciler tarafından "Expand Macro" üzerinden incelendiği için elle yazılmış kod kadar okunaklı olmalıdır.

Kaynaklar

Swift Macros pratikte neleri mümkün kılar

Swift Macros, statik tip güvenliğini koruyarak boilerplate'i ortadan kaldırmak için güçlü bir araçtır. Bu teknoloji şunları mümkün kılar:

  • İki makro kategorisi: freestanding (#) ve attached (@)
  • Swift 6'da altı attached rolü: peer, accessor, member, memberAttribute, extension ve yeni body rolü
  • swift-syntax 600.0.0+ ve AST manipülasyonu ile uygulama
  • SwiftSyntaxMacrosTestSupport ile zorunlu testler, artık Swift Testing desteğiyle
  • Uygulamalar için ayrı paket gereksinimi
  • Xcode'daki "Expand Macro" üzerinden hata ayıklama
  • Geliştirici deneyimi için kritik olan açık hata mesajları

Swift Macros özellikle uyumların (Equatable, Codable) üretilmesinde, ileri seviye property wrapper oluşturmada, imzalardan RPC implementasyonları sentezlemede ve callback tabanlı API'leri async/await yönüne modernize etmede oldukça yararlıdır.

Pratik yapmaya başla!

Mülakat simülatörleri ve teknik testlerle bilgini test et.

Günün meydan okuması

iOS kodundaki hatayı bulabilir misin?

Gerçek bir kod parçası, gizli bir hata, günde bir deneme. Denemek için hesap gerekmez.

Anthony Fillion-Maillet

Yazan:

Anthony Fillion-Maillet

SharpSkill kurucusu

10 yılı aşkın süredir fullstack geliştirici. SharpSkill’i yönetiyor ve burada yayımlanan her şeyden sorumlu.

23 Ağustos 2026 tarihinde güncellendi

Etiketler

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

Paylaş

İlgili makaleler