티스토리 뷰
- 문서
- 문서 ⭐️
- WWDC 23 > Write Swift macros
📝 Assisted by GPT
[1] 매크로 템플릿 만들기
Xcode > New > Pacakge > Swift Macro 선택해서 매크로 템플릿을 만들 수 있음.
커맨드라인으로도 가능한데, 아래 명령어를 실행시키면 됨.
swift package init --type macro
만들어보면 기본적으로
ㄴ dependency 로 swift-syntax 가 걸려있음.
ㄴ WWDC 에서 소개하는 stringify 매크로의 선언, 구현, 예제, 테스트 코드 들어가 있음.
[2] 매크로 유형
일단 크게 두가지
1️⃣ Freestanding Macros
• 코드 블록 외부에서 동작.
• 예: 디버깅 정보를 자동으로 추가.
• 사용 예:
#debugLog("Message")
2️⃣ Attached Macros
• 클래스, 구조체, 열거형 등에 속성처럼 부착되어 동작.
• 예: @Codable 매크로로 Codable 구현 자동 생성.
• 사용 예:
@Codable
struct User {
let name: String
let age: Int
}
그리고 세부적으로는 freestanding 2개, attached 5개.
위 유형들은 프로토콜과도 매핑됨.
Macro 프로토콜이 있고
이걸 채택하는 FreestandingMacro, AttachedMacro 프로토콜이 있고
Freestanding 의 하위 유형들 (CodeItem 이 하나 더 추가되었나봄)
AttachedMacro 의 하위 유형들 이 있음 (여기도 더 추가되었네..)
[3] Freestanding 매크로 만들어보기
1) ExpressionMacro
이건 기본템플릿에 구현된 예제로 퉁.
# 선언
# 구현
# 사용
let result = #stringify(1 + 2)
2) DeclarationMacro
repeat 함수를 매크로를 통해 선언하는 예제를 작성. (조금 안와닿을 수 있는 예제이지만, 파라미터 두개를 받아보기 위해..)
# 선언
@freestanding(declaration, names: named(printRepeat))
public macro printRepeatFunction(_ value: String, _ count: Int) = #externalMacro(module: "MyMacroMacros", type: "PrintRepeatFunctionMacro")
# 구현
public struct PrintRepeatFunctionMacro: DeclarationMacro {
public static func expansion(
of node: some FreestandingMacroExpansionSyntax,
in context: some MacroExpansionContext
) -> [DeclSyntax] {
guard let value = node.arguments.first?.expression else {
fatalError("The macro requires a value")
}
guard let repeatCount = node.arguments.last?.expression else {
fatalError("The macro requires a repeat count")
}
return [
"""
func printRepeat() {
for _ in 0..<\(repeatCount) {
print(\(value))
}
}
"""
]
}
}
# 사용
struct SomeStruct {
#printRepeatFunction("HELLO", 3)
}
SomeStruct().printRepeat()
참고로
객체 안에서 매크로로 선언한 function 은 호출가능이지만,
전역 스코프에서 호출하면 에러 발생 (in 템플릿이 제공하는 main 파일)
3) CodeItemMacro (experimental feature)
아직 실험기능이라 써볼 수는 없었음 (swift-syntax 패키지 버전 바꾸면 가능할지도..)
DeclarationMacro 는 class, function 등이 아니라 코드 블럭을 선언하면 에러가 발생하는데,
CodeItemMacro 는 코드블럭을 선언할 수 있는 매크로.
public struct RepeatMacro: CodeItemMacro {
public static func expansion(
of node: some FreestandingMacroExpansionSyntax,
in context: some MacroExpansionContext
) -> [CodeBlockItemSyntax] {
guard let value = node.arguments.first?.expression else {
fatalError("The macro requires a value")
}
guard let repeatCount = node.arguments.last?.expression else {
fatalError("The macro requires a repeat count")
}
return [
"""
{
for _ in 0..<\(repeatCount) {
print(\(value))
}
}
"""
]
}
}
[4] Attached 매크로 만들어보기
1) PeerMacro
PeerMacro 는 적용된 선언과 동등한 수준(peer level)에 새로운 선언을 추가할 때 사용됩니다.
예를 들어, 클래스나 구조체에 @attached(peer) 매크로를 적용하면, 매크로는 해당 클래스나 구조체의 외부에 새로운 선언을 생성합니다.
# 선언
@attached(peer, names: prefixed(Mock))
public macro Mock() = #externalMacro(module: "MyMacroMacros", type: "MockMacro")
[ names ]
• arbitrary는 이름을 고정하지 않겠다는 선언입니다. (매크로 실행 시 동적으로 결정됩니다)
• 이 경우 매크로가 생성한 선언의 이름은 사용자가 명시적으로 지정하지 않는 한, 컴파일러에 의해 기본 이름 또는 충돌 방지 이름으로 설정될 수 있습니다.
• 사용자로부터 이름을 전달받아 동적으로 생성하는 경우 예시: @Mock(name: "CustomMockUserService")
• prefixed(Mock): 생성된 선언의 이름이 Mock 접두사를 포함해야 합니다.
• 예를 들어, 매크로가 UserService에서 호출되면 MockUserService라는 이름으로 확장됩니다.
# 구현
public struct MockMacro: PeerMacro {
public static func expansion(
of node: AttributeSyntax,
providingPeersOf declaration: some DeclSyntaxProtocol,
in context: some MacroExpansionContext
) throws -> [DeclSyntax] {
// Ensure the macro is applied to a protocol
guard let protocolDecl = declaration.as(ProtocolDeclSyntax.self) else {
fatalError("The macro must be applied to a protocol")
}
let protocolName = protocolDecl.name.text
let mockName = "Mock\(protocolName)"
// 프로퍼티 및 주석에서 기본값 추출
let members = protocolDecl.memberBlock.members.compactMap { member -> String? in
if let variable = member.decl.as(VariableDeclSyntax.self) {
let name = variable.bindings.first?.pattern.description ?? ""
let type = variable.bindings.first?.typeAnnotation?.type.description ?? "Any"
let comment = member.leadingTrivia.compactMap { trivia -> String? in
if case .lineComment(let text) = trivia {
return text.trimmingCharacters(in: .whitespacesAndNewlines)
}
return nil
}.first
let defaultValue = comment?.replacingOccurrences(of: "//", with: "").trimmingCharacters(in: .whitespacesAndNewlines) ?? defaultForType(type)
return "var \(name): \(type) = \(defaultValue)"
} else if let function = member.decl.as(FunctionDeclSyntax.self) {
let name = function.name.text
let params = function.signature.parameterClause.description
let returnType = function.signature.returnClause?.type.description ?? "Void"
return """
func \(name)\(params) -> \(returnType) {
fatalError("Mock implementation for \(name) not provided")
}
"""
}
return nil
}
// Mock 클래스 생성
let mockClass = """
class \(mockName): \(protocolName) {
\(members.joined(separator: "\n"))
}
"""
// Parse the mock class as DeclSyntax
return [
DeclSyntax(stringLiteral: mockClass)
]
}
/// 기본 타입에 따른 기본값 반환
private static func defaultForType(_ type: String) -> String {
switch type {
case "String": return "\"\""
case "Int", "Float", "Double": return "0"
case "Bool": return "false"
case _ where type.hasSuffix("?"): return "nil" // Optional 타입
default: return "fatalError(\"No default value for type: \(type)\")"
}
}
}
# 사용
@Mock
protocol UserService {
// "Default Name"
var name: String { get set }
// 25
var age: Int { get set }
// true
var isActive: Bool { get set }
func fetchUser(id: Int) -> String
}
# Expand Macro
2) AccessorMacro
위의 peer 랑 같이 쓰는 예제.
# 선언
@attached(peer, names: arbitrary)
@attached(accessor)
public macro Logged() = #externalMacro(module: "MyMacroMacros", type: "LoggedMacro")
# 구현
public struct LoggedMacro: AccessorMacro, PeerMacro {
// PeerMacro: 내부 저장 프로퍼티 생성
public static func expansion(
of node: AttributeSyntax,
providingPeersOf declaration: some DeclSyntaxProtocol,
in context: some MacroExpansionContext
) throws -> [DeclSyntax] {
// 매크로가 변수에 적용되었는지 확인
guard let variableDecl = declaration.as(VariableDeclSyntax.self) else {
fatalError("이 매크로는 변수에만 적용할 수 있습니다.")
}
// 프로퍼티 이름과 타입 추출
guard let variableName = variableDecl.bindings.first?.pattern.description else {
fatalError("변수 이름을 확인할 수 없습니다.")
}
guard let type = variableDecl.bindings.first?.typeAnnotation?.type.description else {
fatalError("변수 타입을 확인할 수 없습니다.")
}
// 내부 저장 프로퍼티 생성
let internalPropertyCode = """
private var _\(variableName): \(type) = \(variableDecl.bindings.first?.initializer?.value.description ?? "\(type)()")
"""
// 문자열을 DeclSyntax로 변환
return [DeclSyntax(stringLiteral: internalPropertyCode)]
}
// AccessorMacro: Getter, Setter 추가
public static func expansion(
of node: AttributeSyntax,
providingAccessorsOf declaration: some DeclSyntaxProtocol,
in context: some MacroExpansionContext
) -> [AccessorDeclSyntax] {
guard let variableDecl = declaration.as(VariableDeclSyntax.self) else {
fatalError("이 매크로는 변수에만 적용할 수 있습니다.")
}
guard let variableName = variableDecl.bindings.first?.pattern.description else {
fatalError("변수 이름을 확인할 수 없습니다.")
}
let getter = """
get {
_\(variableName)
}
"""
let setter = """
set {
let oldValue = _name
print("Setting \(variableName) from \\(oldValue) to \\(newValue)")
_\(variableName) = newValue
}
"""
return [
AccessorDeclSyntax(stringLiteral: getter),
AccessorDeclSyntax(stringLiteral: setter)
]
}
}
# 사용
struct User {
@Logged
var name: String
}
var user = User()
user.name = "Bob" // Setting name from to Bob
user.name = "Alice" // Setting name from Bob to Alice
# Expand Macro
'🍏 > Swift' 카테고리의 다른 글
[Swift] @isolated(any) (4) | 2024.11.15 |
---|---|
[Swift] @unchecked, @preconcurrency, @retroactive (9) | 2024.11.06 |
[Swift] Noncopyable (~Copyable) (3) | 2024.10.26 |
[Swift] Typed throws (0) | 2024.10.13 |
[Swift] AsyncStream (1) | 2024.08.24 |
- Total
- Today
- Yesterday
- flutter 앱 출시
- Flutter Clipboard
- Flutter Text Gradient
- Django FCM
- ribs
- METAL
- flutter dynamic link
- 플러터 얼럿
- Flutter 로딩
- ipad multitasking
- drf custom error
- cocoapod
- flutter build mode
- 장고 URL querystring
- 플러터 싱글톤
- Watch App for iOS App vs Watch App
- Flutter Spacer
- PencilKit
- github actions
- Django Firebase Cloud Messaging
- Sketch 누끼
- Flutter getter setter
- 구글 Geocoding API
- Python Type Hint
- SerializerMethodField
- flutter deep link
- 장고 Custom Management Command
- Dart Factory
- Django Heroku Scheduler
- DRF APIException
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |