λ³Έλ¬Έ λ°”λ‘œκ°€κΈ°
πŸ₯– Bread Basics/Swift

Swift 곡식 λ¬Έμ„œ 정리 - 맀크둜 (Macros)

by BreadDev 2025. 4. 11.
728x90

μ•ˆλ…•ν•˜μ„Έμš”! μ˜€λŠ˜μ€ Swift 5.9μ—μ„œ λ„μž…λœ '맀크둜(Macros)'에 λŒ€ν•΄ μ•Œμ•„λ³΄κ² μŠ΅λ‹ˆλ‹€. 맀크둜λ₯Ό μ‚¬μš©ν•˜λ©΄ 반볡적인 μ½”λ“œ μž‘μ„±μ„ 쀄이고, 컴파일 μ‹œμ μ— μ½”λ“œλ₯Ό μžλ™ 생성할 수 μžˆμ–΄ 개발 νš¨μœ¨μ„±κ³Ό μ½”λ“œ ν’ˆμ§ˆμ„ 크게 ν–₯μƒμ‹œν‚¬ 수 μžˆμŠ΅λ‹ˆλ‹€.

πŸ“Œ λ§€ν¬λ‘œλž€?

λ§€ν¬λ‘œλŠ” 컴파일 μ‹œκ°„μ— μ½”λ“œλ₯Ό μƒμ„±ν•˜λŠ” λ„κ΅¬λ‘œ, κ°œλ°œμžκ°€ 직접 μž‘μ„±ν•΄μ•Ό ν•  반볡적인 μ½”λ“œλ₯Ό μžλ™μœΌλ‘œ λ§Œλ“€μ–΄μ€λ‹ˆλ‹€. Swift μ»΄νŒŒμΌλŸ¬λŠ” μ½”λ“œλ₯Ό λΉŒλ“œν•˜κΈ° 전에 λͺ¨λ“  맀크둜λ₯Ό ν™•μž₯ν•©λ‹ˆλ‹€.

맀크둜의 μ£Όμš” νŠΉμ§•:

  • 항상 μ½”λ“œλ₯Ό μΆ”κ°€λ§Œ ν•˜κ³ , κΈ°μ‘΄ μ½”λ“œλ₯Ό μ‚­μ œν•˜κ±°λ‚˜ μˆ˜μ •ν•˜μ§€ μ•ŠμŒ
  • λ¬Έλ²•μ μœΌλ‘œ μœ νš¨ν•œ Swift μ½”λ“œλ₯Ό 생성
  • νƒ€μž… μ•ˆμ „μ„± 보μž₯
  • 맀크둜 ν™•μž₯ 쀑 였λ₯˜ λ°œμƒ μ‹œ 컴파일 였λ₯˜λ‘œ 처리

πŸ“Œ 맀크둜의 μ’…λ₯˜

SwiftλŠ” 두 κ°€μ§€ μœ ν˜•μ˜ 맀크둜λ₯Ό μ œκ³΅ν•©λ‹ˆλ‹€:

1. 독립 맀크둜 (Freestanding Macros)

독립 λ§€ν¬λ‘œλŠ” 이름 μ•žμ— ν•΄μ‹œ 기호(#)λ₯Ό λΆ™μ—¬ ν˜ΈμΆœν•˜λ©°, λ‹€λ₯Έ 선언에 μ²¨λΆ€λ˜μ§€ μ•Šκ³  λ…λ¦½μ μœΌλ‘œ μ‚¬μš©λ©λ‹ˆλ‹€.

func myFunction() {
    print("ν˜„μž¬ μ‹€ν–‰ 쀑인 ν•¨μˆ˜: \(#function)")
    #warning("λ¬Έμ œκ°€ μžˆμŠ΅λ‹ˆλ‹€")
}

이 μ˜ˆμ œμ—μ„œ:

  • #function은 ν˜„μž¬ ν•¨μˆ˜μ˜ μ΄λ¦„μœΌλ‘œ λŒ€μ²΄λ©λ‹ˆλ‹€.
  • #warning은 μ‚¬μš©μž μ •μ˜ 컴파일 κ²½κ³ λ₯Ό μƒμ„±ν•©λ‹ˆλ‹€.

2. 첨뢀 맀크둜 (Attached Macros)

첨뢀 λ§€ν¬λ‘œλŠ” 이름 μ•žμ— μ•³ 기호(@)λ₯Ό λΆ™μ—¬ ν˜ΈμΆœν•˜λ©°, νŠΉμ • 선언에 μ²¨λΆ€λ˜μ–΄ ν•΄λ‹Ή 선언을 μˆ˜μ •ν•©λ‹ˆλ‹€.

// 맀크둜 없이 μž‘μ„±ν•œ μ½”λ“œ
struct SundaeToppings: OptionSet {
    let rawValue: Int
    static let nuts = SundaeToppings(rawValue: 1 << 0)
    static let cherry = SundaeToppings(rawValue: 1 << 1)
    static let fudge = SundaeToppings(rawValue: 1 << 2)
}

// 맀크둜λ₯Ό μ‚¬μš©ν•œ κ°„κ²°ν•œ μ½”λ“œ
@OptionSet<Int>
struct SundaeToppings {
    private enum Options: Int {
        case nuts
        case cherry
        case fudge
    }
}

@OptionSet λ§€ν¬λ‘œλŠ” private μ—΄κ±°ν˜•μ˜ μΌ€μ΄μŠ€λ₯Ό 읽고, 각 μ˜΅μ…˜μ— λŒ€ν•œ μƒμˆ˜λ₯Ό μƒμ„±ν•˜λ©°, OptionSet ν”„λ‘œν† μ½œ μ€€μˆ˜μ„±μ„ μžλ™μœΌλ‘œ μΆ”κ°€ν•©λ‹ˆλ‹€.

πŸ“Œ 맀크둜 ν™•μž₯의 μž‘λ™ 방식

Swiftκ°€ 맀크둜λ₯Ό ν™•μž₯ν•˜λŠ” 과정은 λ‹€μŒκ³Ό κ°™μŠ΅λ‹ˆλ‹€:

  1. μ½”λ“œ νŒŒμ‹±: μ»΄νŒŒμΌλŸ¬κ°€ μ½”λ“œλ₯Ό 읽고 좔상 ꡬ문 트리(AST)λΌλŠ” λ©”λͺ¨λ¦¬ ν‘œν˜„μ„ μƒμ„±ν•©λ‹ˆλ‹€.
  2. 맀크둜 μ‹€ν–‰: μ»΄νŒŒμΌλŸ¬κ°€ AST의 일뢀λ₯Ό 맀크둜 κ΅¬ν˜„μ— μ „μ†‘ν•˜μ—¬ ν™•μž₯을 μš”μ²­ν•©λ‹ˆλ‹€.
  3. μ½”λ“œ λŒ€μ²΄: ν™•μž₯된 μ½”λ“œλ‘œ 맀크둜 ν˜ΈμΆœμ„ λŒ€μ²΄ν•©λ‹ˆλ‹€.
  4. 컴파일 μ§„ν–‰: ν™•μž₯된 μ†ŒμŠ€ μ½”λ“œλ‘œ μ»΄νŒŒμΌμ„ κ³„μ†ν•©λ‹ˆλ‹€.

예λ₯Ό λ“€μ–΄, #fourCharacterCode("ABCD") λ§€ν¬λ‘œλŠ” 4κΈ€μž λ¬Έμžμ—΄μ„ ν•΄λ‹Ήν•˜λŠ” λΆ€ν˜Έ μ—†λŠ” 32λΉ„νŠΈ μ •μˆ˜λ‘œ λ³€ν™˜ν•©λ‹ˆλ‹€:

let magicNumber = #fourCharacterCode("ABCD")
// ν™•μž₯ ν›„: let magicNumber = 1145258561 as UInt32

맀크둜 ν™•μž₯ μ‹œ μ€‘μš”ν•œ λ³΄μ•ˆ 쑰치:

  • 맀크둜 κ΅¬ν˜„μ€ 맀크둜 호좜 λΆ€λΆ„μ˜ AST만 μ ‘κ·Ό κ°€λŠ₯
  • 파일 μ‹œμŠ€ν…œμ΄λ‚˜ λ„€νŠΈμ›Œν¬μ— μ ‘κ·Όν•  수 μ—†λŠ” μƒŒλ“œλ°•μŠ€ ν™˜κ²½μ—μ„œ μ‹€ν–‰
  • 맀크둜 ν™•μž₯은 μž…λ ₯ μ™Έμ˜ μ™ΈλΆ€ μš”μ†Œ(예: ν˜„μž¬ μ‹œκ°„)에 μ˜μ‘΄ν•˜μ§€ μ•Šμ•„μ•Ό 함

πŸ“Œ 맀크둜 μ„ μ–Έν•˜κΈ°

λ§€ν¬λ‘œλŠ” macro ν‚€μ›Œλ“œλ₯Ό μ‚¬μš©ν•˜μ—¬ μ„ μ–Έν•©λ‹ˆλ‹€:

@attached(member)
@attached(extension, conformances: OptionSet)
public macro OptionSet<RawType>() = 
        #externalMacro(module: "SwiftMacros", type: "OptionSetMacro")

맀크둜 μ„ μ–Έμ˜ μ£Όμš” μš”μ†Œ:

  • 맀크둜 μ—­ν• (Role): @attached λ˜λŠ” @freestanding μ†μ„±μœΌλ‘œ μ§€μ •
  • 이름과 μ œλ„€λ¦­ λ§€κ°œλ³€μˆ˜: μœ„ μ˜ˆμ‹œμ—μ„œλŠ” OptionSet<RawType>
  • κ΅¬ν˜„ μœ„μΉ˜: #externalMacro 맀크둜둜 κ΅¬ν˜„ λͺ¨λ“ˆκ³Ό νƒ€μž…μ„ μ§€μ •

맀크둜 μ—­ν•  μ§€μ •

첨뢀 λ§€ν¬λ‘œλŠ” @attached μ†μ„±μœΌλ‘œ 역할을 μ§€μ •ν•©λ‹ˆλ‹€:

@attached(member, names: named(RawValue), named(rawValue), 
        named(`init`), arbitrary)
@attached(extension, conformances: OptionSet)
public macro OptionSet<RawType>() = 
        #externalMacro(module: "SwiftMacros", type: "OptionSetMacro")

독립 λ§€ν¬λ‘œλŠ” @freestanding μ†μ„±μœΌλ‘œ 역할을 μ§€μ •ν•©λ‹ˆλ‹€:

@freestanding(expression)
public macro line<T: ExpressibleByIntegerLiteral>() -> T = 
        /* ... 맀크둜 κ΅¬ν˜„ μœ„μΉ˜... */

πŸ“Œ 맀크둜 κ΅¬ν˜„ν•˜κΈ°

맀크둜λ₯Ό κ΅¬ν˜„ν•˜κΈ° μœ„ν•΄μ„œλŠ” 두 κ°€μ§€ ꡬ성 μš”μ†Œκ°€ ν•„μš”ν•©λ‹ˆλ‹€:

  1. 맀크둜 ν™•μž₯을 μˆ˜ν–‰ν•˜λŠ” νƒ€μž…
  2. 맀크둜λ₯Ό API둜 λ…ΈμΆœν•˜λŠ” 라이브러리

Swift Package Manager둜 맀크둜 μƒμ„±ν•˜κΈ°

μƒˆ 맀크둜 νŒ¨ν‚€μ§€ 생성:

swift package init --type macro

κΈ°μ‘΄ ν”„λ‘œμ νŠΈμ— 맀크둜 μΆ”κ°€:

  1. Package.swift νŒŒμΌμ—μ„œ Swift 도ꡬ 버전을 5.9 μ΄μƒμœΌλ‘œ μ„€μ •
  2. CompilerPluginSupport λͺ¨λ“ˆ κ°€μ Έμ˜€κΈ°
  3. μ΅œμ†Œ 배포 λŒ€μƒμ— macOS 10.15 포함
  4. 맀크둜 κ΅¬ν˜„ 및 라이브러리 νƒ€κ²Ÿ μΆ”κ°€
 
// swift-tools-version: 5.9

import PackageDescription
import CompilerPluginSupport

let package = Package(
    name: "MyPackage",
    platforms: [.iOS(.v17), .macOS(.v13)],
    // ...
    dependencies: [
        .package(url: "https://github.com/swiftlang/swift-syntax", from: "509.0.0")
    ],
    targets: [
        // 맀크둜 κ΅¬ν˜„ νƒ€κ²Ÿ
        .macro(
            name: "MyProjectMacros",
            dependencies: [
                .product(name: "SwiftSyntaxMacros", package: "swift-syntax"),
                .product(name: "SwiftCompilerPlugin", package: "swift-syntax")
            ]
        ),
        // 맀크둜λ₯Ό API둜 λ…ΈμΆœν•˜λŠ” 라이브러리 νƒ€κ²Ÿ
        .target(name: "MyProject", dependencies: ["MyProjectMacros"]),
    ]
)

맀크둜 κ΅¬ν˜„ 예제

#fourCharacterCode 맀크둜 κ΅¬ν˜„:

import SwiftSyntax
import SwiftSyntaxMacros

public struct FourCharacterCode: ExpressionMacro {
    public static func expansion(
        of node: some FreestandingMacroExpansionSyntax,
        in context: some MacroExpansionContext
    ) throws -> ExprSyntax {
        guard let argument = node.argumentList.first?.expression,
              let segments = argument.as(StringLiteralExprSyntax.self)?.segments,
              segments.count == 1,
              case .stringSegment(let literalSegment)? = segments.first
        else {
            throw CustomError.message("정적 λ¬Έμžμ—΄μ΄ ν•„μš”ν•©λ‹ˆλ‹€")
        }

        let string = literalSegment.content.text
        guard let result = fourCharacterCode(for: string) else {
            throw CustomError.message("μœ νš¨ν•˜μ§€ μ•Šμ€ 4κΈ€μž μ½”λ“œμž…λ‹ˆλ‹€")
        }

        return "\(raw: result) as UInt32"
    }
}

private func fourCharacterCode(for characters: String) -> UInt32? {
    guard characters.count == 4 else { return nil }

    var result: UInt32 = 0
    for character in characters {
        result = result << 8
        guard let asciiValue = character.asciiValue else { return nil }
        result += UInt32(asciiValue)
    }
    return result
}

enum CustomError: Error { case message(String) }

맀크둜 ν”ŒλŸ¬κ·ΈμΈ μ§„μž…μ :

import SwiftCompilerPlugin

@main
struct MyProjectMacros: CompilerPlugin {
    var providingMacros: [Macro.Type] = [FourCharacterCode.self]
}

πŸ“Œ 맀크둜 개발과 디버깅

λ§€ν¬λ‘œλŠ” ν…ŒμŠ€νŠΈ 기반 κ°œλ°œμ— μ ν•©ν•©λ‹ˆλ‹€:

  • μ™ΈλΆ€ μƒνƒœμ— μ˜μ‘΄ν•˜μ§€ μ•ŠμŒ
  • λ¬Έμžμ—΄μ—μ„œ μ‰½κ²Œ ꡬ문 λ…Έλ“œ 생성 κ°€λŠ₯
  • AST의 description μ†μ„±μœΌλ‘œ κ²°κ³Ό 확인 κ°€λŠ₯

ν…ŒμŠ€νŠΈ 예제:

let source: SourceFileSyntax =
    """
    let abcd = #fourCharacterCode("ABCD")
    """

let file = BasicMacroExpansionContext.KnownSourceFile(
    moduleName: "MyModule",
    fullFilePath: "test.swift"
)

let context = BasicMacroExpansionContext(sourceFiles: [source: file])

let transformedSF = source.expand(
    macros:["fourCharacterCode": FourCharacterCode.self],
    in: context
)

let expectedDescription =
    """
    let abcd = 1145258561 as UInt32
    """

precondition(transformedSF.description == expectedDescription)

πŸ“Œ μ‹€μš©μ μΈ 맀크둜 ν™œμš© 사둀

λ§€ν¬λ‘œλŠ” λ‹€μŒκ³Ό 같은 μž‘μ—…μ— 특히 μœ μš©ν•©λ‹ˆλ‹€:

  1. μƒμš©κ΅¬ μ½”λ“œ 생성: JSON 직렬화, μ½”λ”© ν‚€ 생성 λ“±
  2. λ””μžμΈ νŒ¨ν„΄ κ΅¬ν˜„: 싱글톀, νŒ©ν† λ¦¬, λΉŒλ” νŒ¨ν„΄ λ“±
  3. ν”„λ‘œν† μ½œ μ€€μˆ˜ μžλ™ν™”: Equatable, Hashable, Codable λ“±
  4. λ‘œκΉ… 및 디버깅 지원: ν•¨μˆ˜ 좔적, μ„±λŠ₯ μΈ‘μ • λ“±
  5. DSL(Domain-Specific Language) λ§Œλ“€κΈ°: SwiftUI μŠ€νƒ€μΌμ˜ 선언적 ꡬ문 λ“±

πŸ“Œ 맀크둜 μ‚¬μš© μ‹œ μ£Όμ˜μ‚¬ν•­

  1. 맀크둜λ₯Ό κ³Όλ„ν•˜κ²Œ μ‚¬μš©ν•˜μ§€ λ§ˆμ„Έμš”: ν•„μš”ν•œ κ²½μš°μ—λ§Œ μ‚¬μš©
  2. 맀크둜 ν™•μž₯ μ½”λ“œλ₯Ό μ΄ν•΄ν•˜μ„Έμš”: μƒμ„±λœ μ½”λ“œκ°€ 무엇인지 μ•Œμ•„μ•Ό 함
  3. 맀크둜 λ¬Έμ„œν™”λ₯Ό μ² μ €νžˆ ν•˜μ„Έμš”: λ™μž‘ 방식과 μƒμ„±ν•˜λŠ” μ½”λ“œλ₯Ό λͺ…ν™•νžˆ μ„€λͺ…
  4. ν…ŒμŠ€νŠΈλ₯Ό μž‘μ„±ν•˜μ„Έμš”: λ‹€μ–‘ν•œ μž…λ ₯에 λŒ€ν•œ 맀크둜 λ™μž‘μ„ ν…ŒμŠ€νŠΈ
  5. 맀크둜 κ΅¬ν˜„μ—μ„œ μ™ΈλΆ€ μƒνƒœμ— μ˜μ‘΄ν•˜μ§€ λ§ˆμ„Έμš”: 결정적인 κ²°κ³Ό 생성

πŸ“Œ 정리

Swift λ§€ν¬λ‘œλŠ” 컴파일 μ‹œκ°„μ— μ½”λ“œλ₯Ό μƒμ„±ν•˜μ—¬ 반볡적인 μž‘μ—…μ„ μžλ™ν™”ν•˜κ³  μ½”λ“œ ν’ˆμ§ˆμ„ ν–₯μƒμ‹œν‚€λŠ” κ°•λ ₯ν•œ λ„κ΅¬μž…λ‹ˆλ‹€. 독립 λ§€ν¬λ‘œμ™€ 첨뢀 λ§€ν¬λ‘œλΌλŠ” 두 κ°€μ§€ μœ ν˜•μ„ 톡해 λ‹€μ–‘ν•œ μ‚¬μš© 사둀λ₯Ό μ§€μ›ν•˜λ©°, μ—„κ²©ν•œ μ•ˆμ „μ„± 보μž₯을 μ œκ³΅ν•©λ‹ˆλ‹€.

맀크둜λ₯Ό 적절히 ν™œμš©ν•˜λ©΄:

  • μ½”λ“œ 쀑볡을 쀄일 수 μžˆμŠ΅λ‹ˆλ‹€
  • μ‹€μˆ˜λ₯Ό λ°©μ§€ν•  수 μžˆμŠ΅λ‹ˆλ‹€
  • μ½”λ“œ 가독성을 ν–₯μƒμ‹œν‚¬ 수 μžˆμŠ΅λ‹ˆλ‹€
  • 개발자 생산성을 높일 수 μžˆμŠ΅λ‹ˆλ‹€

Swift λ§€ν¬λ‘œλŠ” 아직 λ°œμ „ 쀑인 κΈ°λŠ₯μ΄μ§€λ§Œ, 이미 λ§Žμ€ 잠재λ ₯을 보여주고 μžˆμŠ΅λ‹ˆλ‹€. λ³΅μž‘ν•œ μƒμš©κ΅¬ μ½”λ“œλ₯Ό κ°„κ²°ν•˜κ²Œ λ§Œλ“€κ³  더 ν‘œν˜„λ ₯ μžˆλŠ” APIλ₯Ό μ„€κ³„ν•˜λŠ” 데 맀크둜λ₯Ό ν™œμš©ν•΄ λ³΄μ„Έμš”!