IOS/WWDC24
Swift Macro - implement
jjunbbang
2025. 6. 1. 23:32
1. 패키지 생성
Xcode -> File -> New -> Package 선택 시 Swift macro가 있습니다.
2. 구성 보기
패키지 생성 시 기본적으로 #stringfy 메크로 선언, 구현, 단위 테스트 코드가 존재합니다.
3. 메크로 선언
stringify표현에 사용할 매크로 구현체가 있는 모듈을 #externalMacro로 상세한다.
메크로에 사용될 구현체는 @main으로 프로그램 진입 시점에 선언
@main
struct WWDC_MacroPlugin: CompilerPlugin {
let providingMacros: [Macro.Type] = [
StringifyMacro.self,
SlopeSubsetMacro.self
]
}
- 플러그인 메커니즘
- 플러그인은 호스트 프로세스와 swift enum을 JSON 직렬화 형식으로 메시지를 주고받음
- Swift 컴파일러가 플러그인에게 명령을 주고 플러그인은 결과를 되돌려줌
- stdin, stdout 파이프를 통해서 주고받는데, 플러그인은 호스트 프로세스 외 stdin는 막혀있음
- 즉 이 부분에서 메크로 구현체가 맵핑되는걸 선언해 줌
4. 메크로 구현체
stringifyMacro 구현부를 ExpressionMacro 타입을 구현하기 때문에 다음과 같은 프로토콜을 상속해서 구현체를 구현해야 한다.
한 단계 깊이 들어가면 FreestandingMacroExpansionSyntax 프로토콜로 parse에 의해 sytanx tree로 구성된 토큰들을 추출할 수 있습니다.
// MARK: - FreestandingMacroExpansionSyntax
public protocol FreestandingMacroExpansionSyntax: SyntaxProtocol {
/// ### Tokens
///
/// For syntax trees generated by the parser, this is guaranteed to be `#`.
var pound: TokenSyntax {
get
set
}
/// ### Tokens
///
/// For syntax trees generated by the parser, this is guaranteed to be `<identifier>`.
var macroName: TokenSyntax {
get
set
}
var genericArgumentClause: GenericArgumentClauseSyntax? {
get
set
}
/// ### Tokens
///
/// For syntax trees generated by the parser, this is guaranteed to be `(`.
var leftParen: TokenSyntax? {
get
set
}
var arguments: LabeledExprListSyntax {
get
set
}
/// ### Tokens
///
/// For syntax trees generated by the parser, this is guaranteed to be `)`.
var rightParen: TokenSyntax? {
get
set
}
var trailingClosure: ClosureExprSyntax? {
get
set
}
var additionalTrailingClosures: MultipleTrailingClosureElementListSyntax {
get
set
}
}
5. 메크로 테스트
메크로에 사용하는 테스트 코드는 단위 테스트에 용이하도록 설계되어 있습니다.
assertMacroExpansion를 사용해 테스트하는데 메서드 시그니처는 다음과 같습니다.
func assertMacroExpansion(
_ originalSource: String,
expandedSource expectedExpandedSource: String,
diagnostics: [DiagnosticSpec] = [],
macros: [String : any Macro.Type],
applyFixIts: [String]? = nil,
fixedSource expectedFixedSource: String? = nil,
testModuleName: String = "TestModule",
testFileName: String = "test.swift",
indentationWidth: Trivia = .spaces(4),
file: StaticString = #filePath,
line: UInt = #line
)
다음은 사용법 코드입니다.
import WWDC_MacroMacros
let testMacros: [String: Macro.Type] = [
"stringify": StringifyMacro.self, // 메크로 맵핑된 구현체를 dictionary형태로
]
#endif
final class WWDC_MacroTests: XCTestCase {
func testMacro() throws {
#if canImport(WWDC_MacroMacros)
assertMacroExpansion(
"""
#stringify(a + b) // 매크로 표현식
""",
expandedSource: """
(a + b, "a + b") // 예상되는 확장 코드
""",
macros: testMacros
)
#else
throw XCTSkip("macros are only supported when running tests for the host platform")
#endif
}
}