---
id: ios-swift-macros-deep
title: Swift Macros — compile-time codegen
category: Coding
status: draft
source_trust_level: B
verification_status: conceptual
created_at: 2026-05-09
updated_at: 2026-05-09
tags: [ios, swift, vibe-coding]
tech_stack: { language: "Swift", applicable_to: ["iOS"] }
applied_in: []
aliases: [Swift Macros, freestanding macro, attached macro, SwiftSyntax, codegen]
---

# Swift Macros

> Swift 5.9+ 의 hygenic codegen. **Freestanding (`#stringify(x)`) + attached (`@Observable`)**. Boilerplate ↓.

## 📖 핵심 개념
- Compile-time expansion (no runtime).
- Hygenic (scope 격리).
- SwiftSyntax 가 backbone.
- Type-safe.

## 💻 코드 패턴

### Freestanding macro
```swift
let (result, source) = #stringify(2 + 3)
print(result)  // 5
print(source)  // "(2 + 3)"
```

→ `#stringify` 가 expression + source string.

### @Observable (Apple)
```swift
import Observation

@Observable
class UserModel {
    var name: String = ""
    var age: Int = 0
}

// Auto-generates:
// - Tracking infrastructure
// - withMutation calls
// - Observer registration
```

→ SwiftUI 가 자동 observe.

### @Model (SwiftData)
```swift
import SwiftData

@Model
class Item {
    var name: String
    var quantity: Int
    
    init(name: String, quantity: Int) {
        self.name = name
        self.quantity = quantity
    }
}

// Auto-generates:
// - Persistence
// - Relationships
// - Identity
```

### Custom macro 작성
```swift
// Package.swift
import CompilerPluginSupport
import PackageDescription

let package = Package(
    name: "MyMacros",
    platforms: [.macOS(.v13)],
    products: [
        .library(name: "MyMacros", targets: ["MyMacros"]),
    ],
    dependencies: [
        .package(url: "https://github.com/apple/swift-syntax.git", from: "509.0.0"),
    ],
    targets: [
        .macro(
            name: "MyMacroPlugin",
            dependencies: [
                .product(name: "SwiftSyntaxMacros", package: "swift-syntax"),
                .product(name: "SwiftCompilerPlugin", package: "swift-syntax"),
            ]
        ),
        .target(name: "MyMacros", dependencies: ["MyMacroPlugin"]),
    ]
)
```

### Stringify implementation
```swift
import SwiftSyntax
import SwiftSyntaxMacros

public struct StringifyMacro: ExpressionMacro {
    public static func expansion(
        of node: some FreestandingMacroExpansionSyntax,
        in context: some MacroExpansionContext
    ) -> ExprSyntax {
        guard let arg = node.arguments.first?.expression else {
            return ""
        }
        return "(\(arg), \(literal: arg.description))"
    }
}
```

```swift
// MyMacros 모듈
@freestanding(expression)
public macro stringify<T>(_ value: T) -> (T, String) = #externalMacro(module: "MyMacroPlugin", type: "StringifyMacro")
```

### Attached macro
```swift
@AddInit
struct User {
    let name: String
    let age: Int
}

// Auto-generates:
// init(name: String, age: Int) {
//     self.name = name
//     self.age = age
// }
```

```swift
public struct AddInitMacro: MemberMacro {
    public static func expansion(...) throws -> [DeclSyntax] {
        // Generate init from struct properties.
    }
}
```

### @Test (Swift Testing)
```swift
import Testing

@Test
func addTwoNumbers() {
    #expect(1 + 1 == 2)
}

@Test(arguments: [(1, 2, 3), (5, 5, 10)])
func add(_ a: Int, _ b: Int, _ expected: Int) {
    #expect(a + b == expected)
}
```

→ XCTest 의 modern.

### Use case
```
- @Observable: SwiftUI state.
- @Model: SwiftData persistence.
- @Test: testing.
- Custom: builder, Codable boilerplate, lens.

→ Boilerplate 가 큰 → macro 의 답.
```

### Power 와 cost
```
+ Boilerplate 줄임.
+ Type-safe.
+ Compile-time error.

- Compile time ↑.
- Debug 어려움 (expanded code).
- Learning curve.

→ Library author 가 자주 작성.
App dev 가 사용 만 흔함.
```

### Diagnostics
```swift
public static func expansion(...) throws -> [DeclSyntax] {
    guard let structDecl = declaration.as(StructDeclSyntax.self) else {
        throw MacroError.notStruct
    }
    
    if structDecl.members.isEmpty {
        context.diagnose(Diagnostic(
            node: declaration,
            message: SimpleDiagnosticMessage(
                message: "Struct has no members",
                diagnosticID: .init(domain: "MyMacros", id: "empty"),
                severity: .error
            )
        ))
    }
    // ...
}
```

→ Compile error in Xcode.

### Expanded code 보기
```
Xcode Editor → Right-click → Expand Macro.
또는:
swift -dump-ast file.swift
```

→ Generated code 검토.

### Conformance macro
```swift
@AddCodable
struct User {
    let name: String
}

// Generates: Codable conformance.
```

→ Boilerplate ↓ (Swift 가 자동 Codable 도 OK 가, custom logic 필요 시 macro).

### Macro vs property wrapper
```
Property wrapper:
- Runtime.
- 작은 logic.
- 매 access 가 cost.

Macro:
- Compile-time.
- 큰 codegen.
- 0 runtime cost.

→ @Published (옛 Combine) → @Observable (modern macro).
```

### Macro vs protocol extension
```
Protocol extension:
- 다이나믹 dispatch.
- 매 type 가 자체.

Macro:
- 정적 codegen.
- 정밀 control.

→ 매 use case 가 다름.
```

### 함정
```
- Compile time 폭발 (큰 macro).
- Diagnostics 가 confusing.
- Macro 의 변경 = 모든 user recompile.
- Test 가 어려움 (compile-time).
- IDE auto-complete 가 약함.
```

### Production examples
```
- @Observable (Apple, Swift Observation framework).
- @Model (SwiftData).
- @Test (Swift Testing).
- @AsyncFailable (실험).
- TCA (The Composable Architecture)의 @Reducer.
```

### Library author 의 use case
```
- Codable 보다 정밀 JSON.
- Protocol witness table.
- Dependency injection.
- DSL builder.
- Lens / optic.
```

### Compile time
```
큰 macro = 큰 expansion = 큰 compile.
- 10 macro × 1000 line = 10000 line generated.
- Incremental compile 가 partial.
```

→ Profile + optimize.

### Macro testing
```swift
import SwiftSyntaxMacrosTestSupport

func testStringify() {
    assertMacroExpansion(
        '''
        #stringify(1 + 2)
        ''',
        expandedSource: '''
        (1 + 2, "1 + 2")
        ''',
        macros: ['stringify': StringifyMacro.self]
    )
}
```

→ Snapshot test.

### vs Sourcery / GYB (옛)
```
Sourcery: 외부 tool, code generation.
GYB: Apple internal.
Swift Macros: native, type-safe.

→ Macro 가 modern.
```

### Future
```
2026: Macros 가 mainstream.
- 더 많은 framework 가 macro.
- TCA / Vapor 가 macro 채택.
- Codegen ecosystem 발달.
```

## 🤔 의사결정 기준
| 작업 | 추천 |
|---|---|
| State management | @Observable |
| Persistence | @Model (SwiftData) |
| Testing | @Test |
| Boilerplate (init, codable) | Custom macro |
| 작은 codegen | Property wrapper |
| 외부 tool | Sourcery (legacy) |

## ❌ 안티패턴
- **모든 거 macro**: compile time.
- **Diagnostics 없음**: bad UX.
- **Test 없음**: silent break.
- **Macro 의 macro**: complexity.
- **Big logic in macro**: compile slow.

## 🤖 LLM 활용 힌트
- Swift 5.9+ macro.
- Apple 의 @Observable / @Model / @Test.
- Library author 의 답.
- SwiftSyntax + SwiftCompilerPlugin.

## 🔗 관련 문서
- [[iOS_Swift_Macros]]
- [[iOS_SwiftData_Patterns]]
- [[iOS_Swift_Concurrency_async_await]]
