
👋 들어가기 전
슬슬 새로운 개념들을 조금 씩 배워나가야할 것 같다..
"매크로" 해야지 해야지 했는데 선 뜻 시작하기 힘들었던 개념인 것 같다.
이번 포스팅 역시 한번에 작성이 끝나지 않을 수 있을 것 같은 예감이 든다.
시작이 반이니 일단 시작해보자.
🏁 학습할 내용
- 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)
함수, 변수, 타입등 과 같이 하나 이상의 선언을 생성한다.
⚙️ 예시
- #warning(_ message: String): 컴파일 동안 경고 메시지를 제공한다.
@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 선언문에 붙히면 init와 dictionary 같이 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
- dependencies
- MyMacro: 외부 공개 API 선언
- dependencies
- MyMacroMacros (구현)
- dependencies
- MyMacroClient: 동작 테스트 , 클라이언트에서 사용될 때 상황을 시뮬레이션
- dependencies
- Mymacro(선언)
- dependencies
- MyMacroTests: 테스트 코드 작성
- dependencies
- SwiftSyntaxMacrosTestSupport
- dependencies
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: 위 규칙들을 제외한, 자유로운 생성(아직 써보지 않아 정확히 모름 🤷)
- overloaded(attached only)
- 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