매크로부수고 부서지기 - KWDC23

rbw·2023년 9월 24일
0

TIL

목록 보기
92/97

https://www.youtube.com/watch?v=2OCPdw2KTic
위 KWDC23 영상을 보고 정리한 글임니다. 자세한 내용은 위 링크를 봐주세 5


Macro란 ?

특정 입력이 들어왔을 때 특정 출력으로 매핑 되어야 하는지를 정의하는 규칙이나 패턴

하지만 Swift의 매크로는 좀 다름니다.

Macro 도입 이유

구조체에서 생성자를 자동으로 제공해주는 멤버별 이니셜라이저를 예로 설명해주심. 작성하지 않아도 컴파일러 단에서 자동으로 생성해주는 코드이다~

Macro는 컴파일러를 수정하지 않고 Swift 패키지에 배포할 수 있는 방식입니다.

@BestSodeul
struct Person {
    let name: String
    let height: CGFloat

    // let isSodeulBest: Bool = true, 얘는 Macro가 생성해줌
}

Macro의 디자인 철학

  1. Macro를 사용할 때는 명확하게 알 수 있어야 합니다.
  2. Macro에 전달된 코드와, Macro에서 반환되는 코드가 완전하고 오류가 없는지 확인 되어야 한다
    • 매크로는 입력값의 유효성을 검사하고 컴파일러의 경고나 오류를 발생시킬 수 있다는 뜻임니다.
  3. Macro는 확장이 예측 가능해야 하고, 부가적인 방식으로 프로그램에 통합되어야 한다.
  4. Macro는 알 수 없는 마법이 되어선 안 된다.

Macro의 동작 방식

1 + 1을 넣으면 (1 + 1, "1 + 1")을 만들어주는 매크로를 만드려고 함

let calculations = [
    #stringify(1 + 1),
    #stringify(2 - 3),
    #stringify(3 * 5)
]

먼저 컴파일러가 #, @ 기호를 보고 매크로를 판단합니다. 그리고 해당 구현이 포함된 컴파일러 플러그인에 코드를 전송합니다. 그리고 확장이라는 매크로에 의해 생성된 코드조각을 컴파일러가 반환받습니다.

Macro Role

  • @freestanging(expression) -> 값을 반환하는 코드 조각 생성

    • 표현식으로 확장되는 매크로, 실행되고 결과를 생성하는 코드 단위
    • 다른 메서드를 호출하는 것이 아닌, 확장을 코드에 추가하는 것.
  • @freestanging(declaration) -> 하나 이상의 선언을 생성

    • 함수, 변수, 타입과 같이 하나 이상의 선언으로 확장됩니다.
  • @attached(peer) -> 적용된 선언과 함께 새로운 선언을 추가

    • 변수 함수 타입 import 밑 연산자 선언과 같은 어떤 선언에도 첨부 할 수 있으며, 새로운 선언을 삽입
    • 예시로, async를 사용하는 메서드에 적용하여 완료 핸들러 대응도 할 수 있게 만드셨음
  • @attached(accdessor) -> 프로퍼티에 접근자(accessor)를 추가

    • 변수와 서브스크립트에 추가될 수 있으며, get / set / willSet / didSet 등의 접근자를 추가함
  • @attached(memberAttribute) -> 적용된 타입 또는 익스텐션의 선언에 attribute를 추가

    • 타입 또는 extension에 첨부되며, 첨부된 멤버에 특성을 추가
    • 멤버에 각각 매크로를 적용하던 것을 타입에 적용하여 모든 멤버에 적용하게 끔 할 수 이씀
  • @attached(member) -> 적용된 타입 또는 익스텐션 내부에 새로운 선언을 추가

    • 타입 또는 extension에 첨부되어 새로운 메서드, 프로퍼티, 이니셜라이저 등을 추가
  • @attached(conformance) -> 타입 또는 익스텐션에 프로토콜 conformance를 추가

    • 타입 또는 extension에 프로토콜 채택을 추가할 수 있다

Macro 패키지 생성

매크로는 라이브러리와 비슷하게 패키지 형태로 배포가 됩니다.

위에서 살펴본 #stringify의 코드로 살펴보겠습니다.

@freestanding(expression)
public macro stringify<T>(_ value: T) -> (T, String) 
    = #eternalMacro(module: "KWDCMacros", type: "StringifyMacro")

이 처럼 별도의 플러그인에서 매크로를 확장하도록 관계를 정의해야 합니다. (KWDCMacros 모듈의 StringifyMacro 타입을 살펴봐라 라는 뜻)

KWDCMacros에서 이제 매크로의 실제 구현을 담당합니다.

다음 KWDCClient 에서는 매크로의 동작을 테스트할 수 있습니다. import KWDC를 하고 Client 프로그램에서 사용될 매크로를 API 형태로 노출합니다.

Macro 구현

위에 살펴본 부분에서 member role에 관해서 설명해주셨슴

  • import SwiftSyntax: 소스 코드를 특별한 트리 구조로 표현합니다. 다 토큰으로 변환해주는둣
    • Token: 소스 파일의 특정 텍스트를 나타내고 공백 및 주석과 같은 주변의 사소한 정보를 포함 함니다
  • import SwiftSyntaxMacros: 매크로 작성에 필요한 프로토콜과 타입 제공
  • import SwiftSyntaxBuilder: Syntax Tree 구성을 위한 편리한 api 제공

에러 처리

위 매크로를 enum에 적용했을 때 구조체에만 사용해주세요 라는 메시지를 띄우면 더 가독성이 좋아지니까 추가하러 가보겠슴다

위 사진에 구현 부에 아래의 코드를 추가해주면 댐니다

guard declaration.is(StructDeclSyntax.self) else {
    throw MyError.notAStruct
}

더 자세하게 오류를 띄우려면, import SwiftDiagnostics를 추가하여 아래와 같이 해주시믄 됩니다.

  • 오류가 발생한 syntzx node
  • 이를 통해 컴파일러는 어느 줄을 잘못된 것으로 표시할지 알 수 있습니다.

여기서 node란, 개발자가 사용한 매크로가 넘어옵니다.

주의사항

외부 정보를 사용하면 x

  • 매크로는 컴파일러가 제공하는 정보만을 사용해야 합니다. 컴파일러는 매크로 구현이 순수함수라고 가정합니다. 즉, 제공된 데이터가 변경되지 않았따면 확장도 변경되지 않을 것이라고 가정합니다. 이를 우회하면 일관성 없는 동작이 발생할 수 있습니다.

  • 매크로 시스템은 이러한 규칙을 위반할 수 있는 일부 동작을 방지하기 위해 설계되었슴니다. 플러그인은 안전한 샌드박스 내에서 별도의 프로세스로 실행됨니다. 하지만 샌드박스가 모든 동작을 차단하지는 않슴니다~

  • 일반적으로 다른 API들과 같이 라이브러리에 선언되고, 실제 구현은 별도의 플러그인에 되어 있고 이는 안전한 샌드박스에서 실행됩니다.

profile
hi there 👋

0개의 댓글