Notice
Recent Posts
Recent Comments
Link
관리 메뉴

김종권의 iOS 앱 개발 알아가기

[iOS - swift] 11. WWDC2023 정리 - (3) Swift Macro의 expansion (Syntax를 이용하여 매크로 구현방법, literal interpolation, TokenSyntax, ExprSyntax, MacroExpansionContext, 이름 충돌) 본문

WWDC 정리/WWDC 2023 정리

[iOS - swift] 11. WWDC2023 정리 - (3) Swift Macro의 expansion (Syntax를 이용하여 매크로 구현방법, literal interpolation, TokenSyntax, ExprSyntax, MacroExpansionContext, 이름 충돌)

jake-kim 2023. 6. 17. 01:30

(1). Swift Macro의 expansion (Macro의 목적, Macro 모델, Macro Role 이해하기, @freestanding, @attached)

(2). Swift Macro의 expansion (Macro 구현 방법, 올바른 Macro 작성 방법)

(3). Swift Macro의 expansion (Syntax를 이용하여 매크로 구현방법, literal interpolation, TokenSyntax, ExprSyntax, MacroExpansionContext, 이름 충돌)

Syntax를 이용하여 매크로 구현하기

  • 매크로가 올바르게 적용되었는지 확인한 후에도 실제로 확장을 만들어야함
  • SwiftSyntax는 이를 위한 다양한 도구를 제공
    • Syntax node는 immutable하지만 새로운 노드를 만들거나 기존 노드의 수정된 버전을 반환하는 API가 존재
    • SwiftSyntaxBuilder는 하위 노드 중 일부가 trailing closure로 지정되는 SwiftUI 스타일 구문 빌더를 추가

SwiftSyntaxBuilder에서 제공 - SwiftUI스타일의 구문 빌더를 가지고 있는 형테

Syntax로 만들기

  • #unwrap에 대한 매크로 구현 방법

ex) 최종 결과물 

  • guard 부분을 makeGuardStmt() helper 메소드로 따로 분리하고 문자열로 만들기

  • 매개변수로 받는 message를 추가 ("was already checked" 부분)
    • message는 임의의 표현식이므로 ExprSyntax 노드 타입으로 변경

  • guard let 조건은 변수 이름일 뿐이므로 표현식이 아니라 "토큰"이라는 점을 제외하면 이와 유사하며 TokenSyntax 노드 타입인 wrapped 매개변수로 변경

  • unwrap에 실패할 때 "Unexpectedly ..." 코드를 출력하는데, Syntax 노드의 문자열화된 구문을 출력하도록 수정이 필요
    • \(literal:)과 같이 문자열을 이 안에 넣는 special interpolation 방법을 사용
    • 이렇게 사용하면 SwiftSyntax가 문자열의 내용을 문자열 리터럴로 추가

  • original expression에 대한 내용을 가지고 있는 ExprSyntax 파라미터를 추가하여 messagePrefix에 적용
    • ExprSyntax는 description 프로퍼티를 가지고 있기에 이 값을 messagePrefix 변수에 사용

  • "literal:" interpolation을 사용하면 좋은 이유?
    • 문자열에 특수 문자가 포함되어 있는지 자동으로 감지하고 escape를 추가하거나 raw literal로 변환하여 코드가 유효한지 확인
    • 자동으로 유효한지 확인해주기 때문에 올바른 작업을 유도하기가 쉬움

context를 사용하여 파일 이름과 line number 정보 추가하기

  • preconditionFailure에도 file과 line이 있는데 여기에도 Syntax를 이용하여 처리가 가능

  • MacroExpansionContext를 사용하면 소스코드의 file, line 정보를 알 수 있으므로 이것을 사용
    •  context.location(of:)를 사용하면 모든 노드의 위치에 대한 노드 생성이 가능
    • 아래 코드에서 force-unwrap을 사용하는 이유는, originalWrapped는 사용자가 작성한 인수 중 하나이므로 해당 위치는 절대 nil이 아님

  • 이 객체가 가지고 있는 프로퍼티인 file, line을 사용

매크로의 Name Collision

  • 매크로를 사용할 때 외부에 wrappedValue를 선언하고 내부에서도 wrappedValue를 사용할 때 wrappedValue는 더 가까운 값을 찾게 됨
    • 개발자가 이런 실수를 안하는게 좋지만, 이러한 같은 이름을 사용하지 못하게 매크로 구현 방법이 존재
    • 목적) 매크로 내부의 이름은 외부의 이름과 구별되므로 서로 충돌될 수 없게 만들기

  • Macro Expansion Context를 사용하여 "makeUniqueName"기능을 사용
    • 이 기능을 사용하면 unique이름을 얻을 수 있고 개발자가 실수로 참조하지 않게끔 가능

  • Swift는 왜 매크로 내부의 이름은 외부의 이름과 구별되므로 서로 충돌될 수 없게 만들기 일들을 자동으로 해주지 않는 이유는?
    • 많은 매크로가 외부에서 이름을 사용해야 한다는 것을 알았기 때문에 Swift에서 일부러 자동으로 막지 않은것

ex) DictionaryStorage 매크로 - 매크로 내부의 Dictionary와 외부의 Dictionary가 있을때, 자주사용하는 문자열인데 매크로 안에 있으니까 이 단어를 일일이 다 막으면 사용하는 쪽에서는 매크로 내부를 계속 살펴봐야 하는 번거로움이 있기 때문

name specifiers

  • 매크로 이름을 선언할 때 named와 prefix가 있는데 매크로의 이름을 선언할 때 사용할 수 있는 기능이 별도로 존쟈재

  • 5가지 name specifiers

올바른 매크로 사용법

  • Macro는 컴파일러가 제공하는 정보만 사용해야함
    • Macro 구현은 순수 함수이며 제공된 데이터가 변경되지 않은 경우 expansion도 변경할 수 없다고 가정
    • 이를 어기게 되면 일관되지 않은 동작이 발생할 수 있음
    • Compiler plugin은 매크로 구현이 디스크의 파일을 읽거나 네트워크에 액세스하지 못하도록 하는 sandbox에서 실행됨

매크로 잘못된 사용 1)

  • #comilationDate와 같은 매크로를 작성하면 안됨

매크로 잘못된 사용 2)

  • API를 사용하여 날짜나 난수를 얻거나, expansion의 정보를 전역 변수에 저장하고 다른 expansion에서 사용하는 행위
  • 이러한 코드를 개발자가 작성하면 동작은 하지만, 이렇게되면 macro가 오작동할 수 있음으므로 하지 말것

매크로 테스트

  • macro plugin은 일반적인 Swift 모듈일 뿐이며, 이 의미는 일반적인 unit test를 작성할 수 있다는 의미
  • 테스트 기반 개발은 Swift macro 개발에 매우 효과적인 접근 방식
  • SwiftSyntaxMacrosTestSupport의 assertMacroExpansion은 매크로가 올바른 expansion을 생성하는지 확인이 가능
  • 아래처럼 assertMacroExpansion에 매크로를 작성하면 안에서 사용한 매크로가 올바른 expansion을 생성, 정확하게 입력했는지 확인이 가능

* 참고

https://developer.apple.com/videos/play/wwdc2023/10167/

Comments