Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
Tags
- HIG
- SWIFT
- uiscrollview
- ribs
- RxCocoa
- 리펙토링
- Human interface guide
- MVVM
- Observable
- 스위프트
- 애니메이션
- rxswift
- combine
- 리팩토링
- collectionview
- clean architecture
- UITextView
- Clean Code
- uitableview
- swift documentation
- 클린 코드
- map
- ios
- Xcode
- 리펙터링
- UICollectionView
- swiftUI
- tableView
- Refactoring
- Protocol
Archives
- Today
- Total
김종권의 iOS 앱 개발 알아가기
[iOS - swift] 10. WWDC2023 정리 - (2) Swift Macro의 expansion (Macro 구현 방법, 올바른 Macro 에러 작성 방법, SwiftSyntax, SwiftSyntaxMacros, SwiftSyntaxBuilder) 본문
WWDC 정리/WWDC 2023 정리
[iOS - swift] 10. WWDC2023 정리 - (2) Swift Macro의 expansion (Macro 구현 방법, 올바른 Macro 에러 작성 방법, SwiftSyntax, SwiftSyntaxMacros, SwiftSyntaxBuilder)
jake-kim 2023. 6. 16. 02:00(1). Swift Macro의 expansion (Macro의 목적, Macro 모델, Macro Role 이해하기, @freestanding, @attached)
(2). Swift Macro의 expansion (Macro 구현 방법, 올바른 Macro 에러 작성 방법, #externalMacro, SwiftSyntax,
SwiftSyntaxMacros, SwiftSyntaxBuilder)
매크로 구현부의 위치
- 매크로 구현부는 별도의 모듈인, 컴파일러 플러그인 모듈에 속함
- 매크로는 보통 외부 매크로를 사용하는 형태
- #externalMacro 키워드를 사용하여 외부에 있는 매크로를 사용
- externalMacro는 컴파일러 플러그인에 의해 구현되는 매크로를 의미
- Swift compiler는 compiler plugin에게 매크로에 대한 선언 값을 보내고, 플러그인에서 매크로를 찾아 매크로의 expansion부분을 Swift compiler에게 넘겨주는 형태
- #externalMacro는 compiler plugin에 관한 관계를 정의하는것
- module: 플러그인의 이름
- type: 플러그인 내부 유형의 이름
- 즉 #externalMacro를 사용하면 Swift compiler가 플러그인에게 StringifyMacro라는 유형에 대해 expansion(확장)을 요청하는 것
- #externalMacro를 선언하면, 다른 API와 함께 일반 라이브러리에 들어가지만 매크로 구현은 별도의 컴파일러 플러그인 모듈에 들어감
매크로 구현 방법
예제로 사용할 매크로) DictinoaryStorageMacro
SwiftSyntax 임포트
- 매크로를 구현하는 곳에서 SwiftSyntax 라는 라이브러리를 import
- SwiftSyntax: 소스 코드를 구문 분석, 검사, 조작 및 생성하는데 도움이 되는 swift 프로젝트에서 유지 관리하는 패키지
- SwiftSyntax는 Syntax trees를 생성
- 아래 Person 구조는 "StructDeclSyntax"라는 유형의 인스턴스로 표현하며 각 가지에서는 소스코드에 해당하는 특정 표현식들을 분류한것
- attributes
- modifiers
- structKeyword
- identifer
- memberBlock
- 이러한 tree구조의 구문 노드를 "token"이라고 지칭하고 이름, 키워드, 구두점과 같은 소스 파일의 특정 텍스트를 나타내며 텍스트와 공백 및 주석과 같은 사소한 정보도 포함
- 단, "token"이 아닌 노드들이 예외적으로 존재
- attribtues 속성의 AttributeListSyntax
- AttributeListSyntax 노드
- memberBlock 속성의 MemberDeclBlockSyntax
- memberBlock속성의 MemberDeclBlockSyntax 노드에는 자체 속성에 자식 노드가 있는 형태
- 여는 중괄호 "{" 토큰
- 멤버 MemberDeclListSyntax 토큰
- 닫는 중괄호 "}" 토큰
- 내부적으로 노드의 내용을 계속 탐색하면 결국 각 속성에 대한 노드를 찾아가는 원리
- SwiftSyntax 관련 내용은 아래 세션 참고
- Write Swift Macro 세션에서 소스 코드의 특정 부분이 구문 트리로 표현되는 방법을 파악하기 위한 실용적인 방법이 존재
- SwiftSyntax 패키지 문서
SwiftSyntaxMacros 임포트
- 매크로를 구현하는 곳에서 SwiftSyntaxMacros 라는 라이브러리를 import
- 매크로 작성에 필요한 프로토콜과 유형을 제공하는 라이브러리
SwiftSyntaxBuilder 임포트
- 매크로를 구현하는 곳에서 SwiftSyntaxBuilder를 import
- 새로 생성된 코드를 나타내는 구문 트리를 구성하기 위해서 편리한 API를 제공
MemberMacro 프로토콜 준수
- MemberMacro를 준수하면 매크로가 제공하는 각 역할에 대해 필수로 구현해야되는 요소가 생성
- 이전 포스팅 글에서 알아봤던 매크로의 7가지 role은 각 해당 프로토콜이 있으며 구현은 매크로가 제공하는 각 열할에 대한 프로토콜을 준수하여 구현
- DictionaryStorage 매크로에는 이러한 역할 중 4가지가 있으므로 DictionaryStoarageMacro 유형은 네가지 프로토콜을 준수해야함
(예제에서는 편의상 MemberMacro에 대해서만 준수하도록 구현)
- MemberMacro는 expansion(of:providingMemberOf:in:) 타입 메소드를 구현이 필요
- 해당 메소드의 의미: 매크로가 사용될 때 Swift compiler가 구성원 역할을 확장하기 위해 호출하는 것
- 주의할점) expansion은 instance method가 아닌 static method이며, 이 이유는 Swift가 실제로 DictionaryStorageMacro유형의 인스턴스를 생성하지 않도록 하기 위함
- 즉, DictionaryStoargeMacro는 단순히 method의 컨테이너의 역할만 수행
- 위와같은 expansion methods는 모두 소스 코드에 정의한 SwiftSyntax 노드를 반환
- MemberMacro는 타입에 멤버로 추가할 declaration의 리스트들을 확장하여, expansion method는 [DeclSyntax]를 반환
- 본문 내부를 보면 배열이 생성되는 것을 확인이 가능
- 본문 중 "var dictionary" 같은 경우는 단순히 문자열 리터럴이 아니고, Swift는 실제로 이를 소스 코드의 조각으로 취급하고 Swift parser에서 DeclSyntax 노드로 전환하도록 요청
- (이것은 SwiftSyntaxBuilder가 제공해주는 일종의 편리함)
- macro의 다른 세 가지 role에 대한 프로토콜을 extension으로 준수하게하여 DictionaryStorage 매크로의 작업 구현을 완성
올바른 Macro 사용
- 위에서 만든 DictionaryStorageMacro를 enum타입에서 사용한다면?
- 컴파일 에러가 발생하지만, 에러만 보았을때 어떻게 수정해야한다는 의미를 담고 있지 않아서 명확하지가 않은 상태
- 이전 포스팅 글에서 알아보았듯이 Swift macro의 목표 중 하나인 매크로가 입력에서 실수를 감지하고 사용자에게 지정 오류를 내보낼 수 있도록 하는 것이므로 다른 방법이 존재
- ex) @DictionaryStorage는 구조체에만 적용할 수 있도록 명확한 오류 메시지를 생성하도록 매크로 구현부를 수정
- macro 구현에서 보았던 메소드인 expansion의 파라미터를 변경하면 해결
expansion 메소드의 파라미터 개념
- Member macro의 경우 3가지의 파라미터가 존재
- AttributeSyntax
- DeclGroupSyntax
- MacroExpansionContext
- AttribteSytnax 파라미터
- 개발자가 매크로를 사용하기 위해 작성한 실제 DictionaryStorage 속성
- DeclGroupSyntax 파라미터
- struct, enum, class, actor, protocol에 관한 노드가 모두 준수하는 프로토콜 형태
- MacroExpansionContext 파라미터
- 일종의 Context 개체로, 매크로 구현이 Swift compiler와 통신하려고 할 때 사용
- 살펴본 3가지의 파라미터 타입을 사용하여 사용자에게 특정 오류 메시지를 던져주기가 가능
사용자에게 명확한 오류 보내기
- 먼저 declaration 매개변수의 유형을 확인
- struct같은 경우는 StructDeclSyntax
- enum같은 경우는 EnumDeclSyntax
수정후)
- macro 구현부에서 StructDeclSyntax로 타입 체크가 가능
- declaration.is()메소드를 사용
- 지금은 guard문 안에서 빈 배열을 리턴하고 있기 때문에 사용자에게 "struct만 사용해"라는 메시지를 던져주지는 않음
- throw를 사용하여 에러를 던지도록 수정
- 아직 부족한 점: 현재는 타입만을 trhow로 던지고 있으며, output을 제어할 수 없는 상태
- 오류를 방출하는 더 좋은 형태?
- Diagnostic(진단) 이라는 유형의 인스턴스를 생성하는것
- DIagnostic은 일종의 컴파일러 용어이며, 부러진 다리의 엑스레이를 보는 의사가 골절을 진단하듯이 부러진 코드의 구문 트리를 보는 컴파일러나 매크로는 오류나 경고를 나타내는것
- Diagnostic은 두 가지 정보가 필요
- node: 컴파일러는 어디서 잘못되었는지 위치는 알고 있으므로, 어떤 속성에서 에러가 발생헀는지 알려주어야 하는데 지금은DictionaryStorage를 속성을 쓰고 있었고 이 정보는 attribute 속성을 넘기기만하면 해결
- message: 컴파일러에서 생성하려는 실제 메세지
- MyLibDiagnostic은 enum으로 따로 정의한 것이며, DiagnosticMessage 프로토콜을 따르는 형태
- 가장 중요한 부분은 severity이며 여기서는 이 진단이 error인지 warning인지 결정이 가능
- 이러한 내용을 정의하여 context.diagnose()에 넘기면 swift compiler에게 알려주어 에러 메시지를 띄우기 가능
- fix-it 기능도 제공 가능
(다음 포스팅 글에서 Building syntax trees 계속)
* 참고
https://swiftpackageindex.com/apple/swift-syntax/508.0.1/documentation/swiftsyntax
'WWDC 정리 > WWDC 2023 정리' 카테고리의 다른 글
Comments