Hamp 2025. 6. 28. 00:09
반응형

👋 들어가기 전

슬슬 새로운 개념들을 조금 씩 배워나가야할 것 같다..

"매크로" 해야지 해야지 했는데 선 뜻 시작하기 힘들었던 개념인 것 같다.

 

이번 포스팅 역시 한번에 작성이 끝나지 않을 수 있을 것 같은 예감이 든다.

 

시작이 반이니 일단 시작해보자.

🏁 학습할 내용

  • Overview
    • 매크로 의미
    • 등장 계기
    • 매크로의 종류
  • 동작 방식
  • Freestanding Macro
  • Attached Macro
  • 매크로 만들기
  • SwiftSyntax
    • SwiftSyntaxMacros
    • SwiftSyntaxBuilder
  • 구현부 파헤치기
  • 선언부 파헤치기

📝 Overview

✅ 매크로 의미

컴퓨터 과학에서 매크로 의미는 다음과 같다.

입력 시퀀스가 출력 시퀀스로 매핑되는 규칙 또는 패턴을 의미

 

이 때 사용되는 매핑 패턴(과정)을 Source code에서 확장하는 의미를 갖기 때문에, macroExpand라 불렀고

확장에 사용되는 규칙을 macro라고 불렀다.

🎉 등장 계기

역할

매크로는 컴파일 시점에 소스 코드를 변형하여, 반복적인 코드를 줄이도록 도와준다.

특징

  • 매크로는 항상 덧셈 연산이다. 즉, 매크로는 새로운 코드를 추가할 뿐 기존 코드를 삭제 or 수정하지 않는다.
  • 매크로는 티가 나야한다. (이 부분은 매크로 종류 부분에서 자세히 설명)
  • 매크로는 기존 코드와 매크로를 통해 확장된 코드 컴파일 시점에 검사된다.

🧰 종류

  • Freestanding Macros
  • Attached Macros


🧑‍💻 동작 방식

1) 먼저 매크로를 사용하면 Swift Compiler에서 매크로 사용을 감지한다.

 

2) 컴파일러는 매크로의 구현부가 있는 Compiler Plugin에게 코드를 전달한다.

3) Compiler Plugin는 미리 구현되어 있는 패턴에 맞춰 확장된 코드를 Compiler에게 전달한다. 


🔥 Freestanding Macros

✅ 정의

독립형 매크로를 의미하며, 스스로 동작할 수 있다.

✨특징

  • #으로 시작한다. (매크로 특징 중 티가 나야한다는 특징이 이 부분에서 만족된다)
  • 매크로 혼자 독립적으로 사용될 수 있다.

🧰 종류

 1. freestanding(expression)

값을 반환하는 확장 방식으로, 매크로 결과는 계산된 값을 반환한다.

 

⚙️ 예시

  • #function: 현재 위치한 함수 이름 반환
@freestanding(expression)
macro function<T>() -> T where T : ExpressibleByStringLiteral

2. freestanding(declaration)

함수, 변수, 타입등 과 같이 하나 이상의 선언을 생성한다.

 

⚙️ 예시

@freestanding(declaration)
macro warning(_ message: String)

 

메시지 파라미터로 전달된 문자열 다양한 형태의 warning을 선언을 생성한다.


📮 Attached Macros

✅ 정의

자신이 부착된 선언(타입, 변수, 함수)등을 수정(확장)한다.

✨특징

  • @으로 시작한다. (매크로 특징 중 티가 나야한다는 특징이 이 부분에서 만족된다)
  • 단순 argument만을 이용하지 않고 연결된 선언문에 대한 접근도 가능하다.
  • 새 메서드 추가, 프로토콜 준수 추가 등 다양한 코드를 자동으로 생성한다.

🧰 종류

 1. attached(peer)

모든 선언(변수, 함수, 타입)에 부착될 수 있다.

심지어 import 및 연산자 선언에도 부착될 수 있다.

 

역할은 적용된 선언에 새로운 선언을 추가(삽입)하는 것을 의미한다.

 

이때 삽입되는 위치는 부착된 선언과 같은 레벨에 위치한다.

 

⚙️ 예시

다음과 같이 concurrency 문법으로 작성된 User 정보 패치 API가 있다.

func fetch(_ userName: String) async -> User {
	...
}

 

이때 이전 코드들은 concurrency를 이전 Completion Handler 기법으로 작성되어 있어

Completion Handler 형식으 API를 뚫어줘야한다.

 

이때 만약 서비스에 필요한 API들이 concurrency 문밥과 Completion Handler법 2개가 모두 필요하다면

많이 골치아플  수 있다.

 

이때 attached(peer)를 다음과 같이 이용할 수 있다.

@attached(peer, names: overloaded) // overloaded: 부착된 이름과 동일 이름 사용
macro AddCompletionHandler(parameterName: String = "completionHandler")

 

다음과 같이 새로운 선언을 삽입하는 확장이 적용된다.

// 매크로 사용
@AddCompletionHandler(parameterName: "OnCompletion")
func fetch(_ userName: String) async -> User {
	...
}

// 확장 후 

func fetch(_ userName: String, onCompletion: @escaping (User?) -> Void) async -> User {
	Task.detached { onCompletion(await fetch(userName)) }
}

 2. attached(accessor)

변수 or subscript에 붙어서 get, set, willSet, didSet 같은 접근자를 추가한다.

 

⚙️ 예시

다음 코드는 dictionary 제외한 프로퍼티들의 접근자가 dictionary 프로퍼티에 공통적으로 접근하여

read / write를 진행하고 있다.

 

반복적인 부분이 너무 명확히 보인다. 

이 부분을 attached(accessor) 매크로를 통해 개선해보자.

 

다음과 같이 DictionaryStorage 매크로를 생성한다.

 

그 다음 이렇게 dictionary에 접근하는 프로퍼티에 매크로를 부착해주면, 다음과 같이 새로운 접근자가 추가(확장)된다.

 3. attached(memberAttribute)

타입 or 익스텐션 선언에 붙어서  attribute를 추가한다.

 

⚙️ 예시

memberAttribute를 붙히면 이제 타입과 익스텐션 선언문에 매크로를 쓸 수 있다.

 

아래와 같이 struct 선언문에 붙히면 initdictionary 같이 DictionaryStorage 동작이 필요없거나

이미 따르 attached(accessor) 형태로 붙어있는 birthDate를 제외한 저장 프로퍼티(name, height)에 자동으로

 

@DictionaryStorage를 추가한다.

 

 4. attached(member)

타입 or 익스텐션 선언에 붙어서  새로운 멤버를 추가 선언한다.

 

⚙️ 예시

위 예시에서 dictionary 멤버와 , init(dictionary:)에 대해 매크로로 추가 선언을 한다는 것을 의미

 5. attached(extension)

타입 or 익스텐션 선언에 프로토콜 채택과 새로운 멤버 리스트를 추가 선언한다.

 

⚙️ 예시

지금까지 진행된 모든 attached를 하나의 프로토콜로 묶어서 적용한다.


아래와 같이 자동으로 프로토콜 conformance를 적용하며, 위에 여러개의 role을 순차적으로 적용한다.


✏️ 매크로 만들기

🔨 프로젝트 생성

📂 프로젝트 구조 파악

Package.swift

MyMacro, MyMacroClient는 외부로 공개되는 API되므로

MyMacro 패키지를 설치하면 최대 2개의 Target을 설치할 수 있다.

 

외부의존성인 swift-syntax를 기본적으로 의존하고 있다. 해당 package 역할은 아래에서 알아보자.

Package의 기본적으로 4개의 타겟이 생긴다.

  • macro: 구현
    • dependencies 
      • SwiftSyntaxMacros
      • SwiftCompilerPlugin
  • MyMacro: 외부 공개 API 선언
    • dependencies
      • MyMacroMacros (구현)
  • MyMacroClient: 동작 테스트 , 클라이언트에서 사용될 때 상황을 시뮬레이션
    • dependencies
      • Mymacro(선언)
  • MyMacroTests: 테스트 코드 작성
    • dependencies
      • SwiftSyntaxMacrosTestSupport

MyMacro.swift(선언)

@freestanding(expression) // 매크로 종류 선언 (독립 매크로의 expression 사용)
public macro stringify<T>(_ value: T) -> (T, String) = #externalMacro(module: "MyMacroMacros", type: "StringifyMacro")

// #externalMacro(module: "MyMacroMacros", type: "StringifyMacro") 구현부 연결, 구현부 이름과 모듈 명시

MyMacroMacro.swift(구현)

import SwiftCompilerPlugin
import SwiftSyntax
import SwiftSyntaxBuilder
import SwiftSyntaxMacros

// freestandingMacro의 표현식 매크로를 구현하기위해 ExpressionMacro 프로토콜 채택
public struct StringifyMacro: ExpressionMacro {
    public static func expansion(
        of node: some FreestandingMacroExpansionSyntax,
        in context: some MacroExpansionContext
    ) -> ExprSyntax {
        guard let argument = node.arguments.first?.expression else {
            fatalError("compiler bug: the macro does not have any arguments")
        }

        return "(\(argument), \(literal: argument.description))"
    }
}

@main
struct MyMacroPlugin: CompilerPlugin {
    let providingMacros: [Macro.Type] = [
        StringifyMacro.self, // 외부에 제공될 Macro Type
    ]
}

MyMacroClient/main.swift(클라이언트 시뮬레이션)

import MyMacro // 타겟 추가

let a = 17
let b = 25

let (result, code) = #stringify(a + b)

print("The value \(result) was produced by the code \"\(code)\"")

🔎 Swift Syntax

Swift 컴파일러의 문법 트리(Syntax Tree)를 다루는 라이브러리

즉, Swfit 소스 코드를 트리 형태로 읽고, 탐색하고, 조작할 수 있게 해주는 도구이다.

 

build 시, indexing과정이 이런 트리를 만들어가는 과정인 것 같다.

다음 Person 구조체는 StructDeclSyntax 타입의 형태로 구분된다.

 

StructDeclSyntax는 사진과 같은 형태로 더 작은 형태로 쪼개질 수 있다.

 

  • AttributeListSyntax
    • @DictionaryStorage같은 attribute Syntax
  • TokenSyntax.keyword
    • struct같은 키워드 Syntax
  • TokenSyntax.identifier
    • Person같은 구분자 관련 Syntax
  • memberBlock
    • 멤버 관련 Syntax

SwiftSyntaxMacros

매크로 작성에 필요한 프로토콜과 타입을 제공

 

위에서 채택했던 프로토콜이 바로 이곳에서 제공된다.

// FreestandingMacro
@freestanding(expression) -> ExpressionMacro
@freestanding(declaration) -> DeclarationMacro

// AttachedMacro
@attached(peer) -> PeerMacro
@attached(accessor) -> AccessorMacro
@attached(memberAttribute) -> MemberAtrributeMacro
@attached(member) -> MemberMacro
@attached(extension) -> ExtensionMacro

 

여기서 재밌는 사실은 attached와 freestanding을 조합해서 사용할 수 없다.

 

즉 이런 형태는 불가능하다.

@attached(accessor)
@freestanding(expression)
macro ..

 

SwiftSyntaxBuilder

Syntax Tree 구성을 위한 편리한 API 제공


⌨️ 구현부 파헤치기

위에서 구현 부(MyMacroMacros)를 간단히 넘어갔지만, 이번에는 조금 더 자세히 살펴보자.

 

먼저 freestandingMacro부터 살펴보자.

 

ExpressionMacro

  • node
    • 매크로가 사용되는 구문 트리 노드(arguments 정보 등)이 들어있다.
  • context
    • 매크로 확장이 수행되는 context 정보(파일 경로, 선언 위치, 모듈 정보)들이 들어있으며, 컴파일러와 소통을 위해 사용된다.
  • ExprSyntax(return)
    • 최종적으로 컴파일러가 매크로 확장 시 대체할 Swift 표현식
public struct ExpMacro: ExpressionMacro {
    public static func expansion(
        of node: some FreestandingMacroExpansionSyntax,
        in context: some MacroExpansionContext
    ) -> ExprSyntax {
        guard let argument = node.arguments.first?.expression else {
            fatalError("compiler bug: the macro does not have any arguments")
        }

        return "(\(argument), \(literal: argument.description))"
    }
}

DeclarationMacro

  • node
    • 매크로가 사용되는 구문 트리 노드(arguments 정보 등)이 들어있다.
  • context
    • 매크로 확장이 수행되는 context 정보(파일 경로, 선언 위치, 모듈 정보)들이 들어있으며, 컴파일러와 소통을 위해 사용된다.
  • [DeclSyntax](return)
    • 최종적으로 컴파일러가 매크로 확장 시 대체할 1개 이상의 선언문
public struct DecMacro: DeclarationMacro {
    public static func expansion(
        of node: some FreestandingMacroExpansionSyntax,
        in context: some MacroExpansionContext
    ) throws -> [DeclSyntax] {
        
        // 1. 변수 선언 구문 생성
        let variableDecl: DeclSyntax = """
        let generatedValue = 42
        """
        
        // 2. 함수 선언 구문 생성
        let functionDecl: DeclSyntax = """
        func generatedFunction() {
            print("Generated function called!")
        }
        """
        
        // 3. 생성된 DeclSyntax 배열로 반환
        return [variableDecl, functionDecl]
    }
}

PeerMacro

  • node
    • 매크로가 사용되는 구문 트리 노드(arguments 정보 등)이 들어있다.
  • declaration
    • 붙어 있는 선언 정보(타입)
  • context
    • 매크로 확장이 수행되는 context 정보(파일 경로, 선언 위치, 모듈 정보)들이 들어있으며, 컴파일러와 소통을 위해 사용된다.
  • [DeclSyntax](return)
    • 최종적으로 컴파일러가 매크로 확장 시 대체할 1개 이상의 선언문
public struct AttachedPeerMacro: PeerMacro {
  public static func expansion(
    of node: SwiftSyntax.AttributeSyntax,
    providingPeersOf declaration: some SwiftSyntax.DeclSyntaxProtocol,
    in context: some SwiftSyntaxMacros.MacroExpansionContext
  ) throws -> [SwiftSyntax.DeclSyntax] {

    // Struct 선언에 붙은 경우
    if let structDecl = declaration.as(StructDeclSyntax.self) {
      let structName = structDecl.name
      let peerFunction: DeclSyntax = """
      func describe() {
          print("This is struct named \(structName)")
      }
      """
      return [peerFunction]
    }

    // Variable 선언에 붙은 경우
    if let varDecl = declaration.as(VariableDeclSyntax.self) {
          for binding in varDecl.bindings {
              if let identPattern = binding.pattern.as(IdentifierPatternSyntax.self) {
                  let variableName = identPattern.identifier.text
                  let peerFunction: DeclSyntax = """
                  func describe() {
                      print("This is variable named \(raw: variableName)")
                  }
                  """
                  return [peerFunction]
              }
          }
      }
      
      if let funcDecl = declaration.as(FunctionDeclSyntax.self) {
          let peerFunction: DeclSyntax = """
          func describe() {
              print("This is variable named \(raw: funcDecl.name.text)")
          }
          """
          return [peerFunction]
        }

    // 그 외 (enum, class 등) -> Peer 생성 안함
    return []
  }
}

AccessorMacro

  • node
    • 매크로가 사용되는 구문 트리 노드(arguments 정보 등)이 들어있다.
  • declaration
    • 붙어 있는 선언 정보(타입)
  • context
    • 매크로 확장이 수행되는 context 정보(파일 경로, 선언 위치, 모듈 정보)들이 들어있으며, 컴파일러와 소통을 위해 사용된다.
  • [AccessorDeclSyntax](return)
    • 최종적으로 컴파일러가 매크로 확장 시 대체할 1개 이상의 접근자
public struct AttachedAccessorMacro: AccessorMacro {
  public static func expansion(
    of node: SwiftSyntax.AttributeSyntax,
    providingAccessorsOf declaration: some SwiftSyntax.DeclSyntaxProtocol,
    in context: some SwiftSyntaxMacros.MacroExpansionContext
  ) throws -> [SwiftSyntax.AccessorDeclSyntax] {
    // get accessor 만들기
    let getAccessor: AccessorDeclSyntax = """
    get { 
        return 42 
    }
    """

    // set accessor 만들기
    let setAccessor: AccessorDeclSyntax = """
    set {
        print("New value: \\(newValue)")
    }
    """

    return [getAccessor, setAccessor]
  }
}

 

MemberAttributeMacro

  • node
    • 매크로가 사용되는 구문 트리 노드(arguments 정보 등)이 들어있다.
  • declaration
    • 붙어 있는 선언 정보(타입)
  • member
    • 타입 안의 멤버 (var, let, func)
  • context
    • 매크로 확장이 수행되는 context 정보(파일 경로, 선언 위치, 모듈 정보)들이 들어있으며, 컴파일러와 소통을 위해 사용된다.
  • [AttributeSyntax](return)
    • 최종적으로 컴파일러가 매크로 확장 시 추가될 1개 이상의 attribute 
public struct AttachedMemberAttributeMacro: MemberAttributeMacro {
  public static func expansion(
    of node: SwiftSyntax.AttributeSyntax,
    attachedTo declaration: some SwiftSyntax.DeclGroupSyntax,
    providingAttributesFor member: some SwiftSyntax.DeclSyntaxProtocol,
    in context: some SwiftSyntaxMacros.MacroExpansionContext
  ) throws -> [SwiftSyntax.AttributeSyntax] {
    // 모든 멤버에 다음 속성 추가:
    // @available(*, deprecated, message: "Generated by macro")
    let attribute: AttributeSyntax = """
    @available(*, deprecated, message: "Generated by AttachedMemberAttributeMacro")
    """

    return [attribute]
  }
}

MemberMacro

  • node
    • 매크로가 사용되는 구문 트리 노드(arguments 정보 등)이 들어있다.
  • declaration
    • 붙어 있는 선언 정보(타입)
  • protocols
    • 매크로가 붙은 타입이 채택한 프로토콜 목록
  • context
    • 매크로 확장이 수행되는 context 정보(파일 경로, 선언 위치, 모듈 정보)들이 들어있으며, 컴파일러와 소통을 위해 사용된다.
  • [DeclSyntax](return)
    • 최종적으로 컴파일러가 매크로 확장 시 추가될 1개 이상의 선언문
public struct AttachedMemberMacro: MemberMacro {
  // 1️⃣ 기본 멤버 추가
  public static func expansion(
    of node: AttributeSyntax,
    providingMembersOf declaration: some DeclGroupSyntax,
    in context: some MacroExpansionContext
  ) throws -> [DeclSyntax] {
    let generatedMember: DeclSyntax = """
    var generatedMember: String { "Hello, Macro!" }
    """

    return [generatedMember]
  }

  // 2️⃣ 특정 프로토콜 준수 시 멤버 추가
  public static func expansion(
    of node: AttributeSyntax,
    providingMembersOf declaration: some DeclGroupSyntax,
    conformingTo protocols: [TypeSyntax],
    in context: some MacroExpansionContext
  ) throws -> [DeclSyntax] {
    // 만약 CustomStringConvertible을 채택하면 description 추가
    let conformsToCustomStringConvertible = protocols.contains { type in
        type.description == "CustomStringConvertible"
    }

    guard conformsToCustomStringConvertible else {
        return []
    }

    let descriptionProperty: DeclSyntax = """
    var description: String { "I am a generated description!" }
    """

    return [descriptionProperty]
  }
}

ExtensionMacro

  • node
    • 매크로가 사용되는 구문 트리 노드(arguments 정보 등)이 들어있다.
  • declaration
    • 붙어 있는 선언 정보(타입)
  • type
    •  타입 명(예: MyStruct)
  • protocols
    • 매크로가 붙은 타입이 채택한 프로토콜 목록
  • context
    • 매크로 확장이 수행되는 context 정보(파일 경로, 선언 위치, 모듈 정보)들이 들어있으며, 컴파일러와 소통을 위해 사용된다.
  • [ExtensionDeclSyntax](return)
    • 최종적으로 컴파일러가 매크로 확장 시 추가될 1개 이상의 확장 구문(extension)
public struct AttachedExtensionMacro: ExtensionMacro {
  public static func expansion(
    of node: SwiftSyntax.AttributeSyntax,
    attachedTo declaration: some SwiftSyntax.DeclGroupSyntax,
    providingExtensionsOf type: some SwiftSyntax.TypeSyntaxProtocol,
    conformingTo protocols: [SwiftSyntax.TypeSyntax],
    in context: some SwiftSyntaxMacros.MacroExpansionContext
  ) throws -> [SwiftSyntax.ExtensionDeclSyntax] {

    // 1. declaration 활용: struct/class 이름 가져오기
    let typeName: String
    if let structDecl = declaration.as(StructDeclSyntax.self) {
      typeName = structDecl.name.text
    } else if let classDecl = declaration.as(ClassDeclSyntax.self) {
      typeName = classDecl.name.text
    } else {
        // struct나 class가 아니면 진단 출력
        context.diagnose(Diagnostic(
            node: node,
            message: MacroExpansionErrorMessage("This macro can only be applied to struct or class")
        ))
        return []
    }

    // 2. protocols 활용: 이미 CustomStringConvertible 채택했는지 확인
    let alreadyConforms = protocols.contains { typeSyntax in
        typeSyntax.description == "CustomStringConvertible"
    }

    if alreadyConforms {
        // 이미 채택했으면 warning 출력
        context.diagnose(Diagnostic(
            node: node,
            message: MacroExpansionWarningMessage(
              "\(typeName) already conforms to CustomStringConvertible")
        ))
        return []
    }


    // header: 채택 프로토콜, 클로저에 멤버 선언
    let extDecl = try ExtensionDeclSyntax("extension \(type.trimmed): CustomStringConvertible") {
    """
    var description: String { "I am a generated description!" }
    """
    }

    return [extDecl]
  }
}

매크로 등록

실질적으로 매크로 확장하는 역할을 하는 컴파일러 플러그인에게 매크로를 등록

@main
struct MyMacroPlugin: CompilerPlugin {
    let providingMacros: [Macro.Type] = [
        ExpMacro.self,
        DecMacro.self,
        AttachedPeerMacro.self,
        AttachedAccessorMacro.self,
        AttachedMemberAttributeMacro.self,
        AttachedMemberMacro.self,
        AttachedExtensionMacro.self
    ]
}

📑 선언부 파헤치기

public macro exp<T>(_ value: T) -> (T, String) = #externalMacro(module: "MyMacroMacros", type: "ExpMacro")

@freestanding(declaration, names: named(generatedValue))
public macro dec() = #externalMacro(module: "MyMacroMacros", type: "DecMacro")

@attached(peer, names: overloaded)
public macro peer() = #externalMacro(module: "MyMacroMacros", type: "AttachedPeerMacro")

@attached(accessor)
public macro accessor() = #externalMacro(module: "MyMacroMacros", type: "AttachedAccessorMacro")

@attached(member)
public macro member() = #externalMacro(module: "MyMacroMacros", type: "AttachedMemberMacro")

@attached(memberAttribute)
public macro memberAttribute() = #externalMacro(module: "MyMacroMacros", type: "AttachedMemberAttributeMacro")

@attached(extension, conformances: CustomStringConvertible, names: named(description))
public macro extensionmacro() = #externalMacro(module: "MyMacroMacros", type: "AttachedExtensionMacro")

선언부 파라미터

위애서 보이는 , names, conformances등  파라미터 역할과 각 값의 역할을 알아보자.

  • names: 매크로를 통해 생성되는 멤버 이름
    • overloaded(attached only)
      • 매크로가 부착된 선언 그대로 이름을 가져감, 아래 결과에서 describe 함수를 참고
    • prefixed(<some prefix>)(attached only)
      • 매크로가 부착된 선언의 이름 앞 <some prefix>로 생성 
    • suffixed(<some suffix>)(attached only)
      • 매크로가 부착된 선언의 이름 뒤  <some suffix>로 생성
    • named(<some name>): <some named>으로 전달된 값으로 생성
    • arbitrary: 위 규칙들을 제외한, 자유로운 생성(아직 써보지 않아 정확히 모름 🤷) 
  • conformances: 채택하고 있어야하는 프로토콜 명

🔓 확장 결과

import MyMacro

let a = 17
let b = 25

let (result, code) = #exp(a + b)

print("The value \(result) was produced by the code \"\(code)\"")

// -----------------------------------------------------------------------------

// ❌확장 전 
struct A {
  #dec()
}

// ✅ 확장 후
struct A {
	let generatedValue = 42
}

let aa = A()
print(aa.generatedValue) // 42

// -----------------------------------------------------------------------------

// ❌확장 전 
struct B {
  @peer
  func describe(_ i: Int) {}
}

// ✅ 확장 후
struct B {
  func describe(_ i: Int) {}
  func describe() {
    print("This is variable named describe")
  }
}

let bb = B()
bb.describe() // This is variable named describe

// -----------------------------------------------------------------------------

// ❌확장 전 
struct C {
  @accessor
  var c: Int = 3
}

// ✅ 확장 후
struct C {
  var c: Int = 3
  {
    get {
        return 42
    }
    set {
        print("New value: \(newValue)")
    }
  }
}

// -----------------------------------------------------------------------------

// ❌확장 전 
@member
struct D {
  var d: Int = 4
}

// ✅ 확장 후
struct D {
  var d: Int = 4
  var generatedMember: String {
    "Hello, Macro!"
  }
}

// -----------------------------------------------------------------------------

// ❌확장 전 
@memberAttribute
struct F {
  var f: Int = 3
}

// ✅ 확장 후
struct F {
  var f: Int = 3
  @available(*, deprecated, message: "Generated by AttachedMemberAttributeMacro")
}

// -----------------------------------------------------------------------------
// ❌확장 전 
@extensionmacro
struct G {

}
// ✅ 확장 후
extension G: CustomStringConvertible {
    var description: String {
        "I am a generated description!"
    }
}

출처

https://sujinnaljin.medium.com/swift-%EB%A7%A4%ED%81%AC%EB%A1%9C-5e232b78dc5b

https://docs.swift.org/swift-book/documentation/the-swift-programming-language/macros/

https://developer.apple.com/videos/play/wwdc2023/10167/?time=0

 

반응형