🔥 매크로 선언

493자
7분

Swift 코드에서 함수나 타입 같은 심볼을 구현할 때는 보통 별도의 선언이 필요하지 않습니다. 하지만 매크로의 경우에는 선언과 구현이 분리되어 있답니다. 매크로의 선언에는 이름, 매개변수, 사용 가능한 위치, 생성하는 코드의 종류 등이 포함되어 있습니다. 매크로의 구현에는 Swift 코드를 생성하여 매크로를 확장하는 코드가 포함되어 있죠.

매크로 선언은 macro 키워드로 시작합니다. 예를 들어, 이전 예제에서 사용한 @OptionSet 매크로의 선언 일부는 다음과 같아요:

public macro OptionSet<RawType>() =
        #externalMacro(module: "SwiftMacros", type: "OptionSetMacro")
swift

첫 번째 줄에는 매크로의 이름과 인수가 명시되어 있습니다. 이름은 OptionSet이고, 인수는 없네요. 두 번째 줄에서는 Swift 표준 라이브러리의 externalMacro(module:type:) 매크로를 사용하여 매크로의 구현 위치를 Swift에 알려줍니다. 이 경우에는 SwiftMacros 모듈에 OptionSetMacro라는 타입이 있고, 이 타입이 @OptionSet 매크로를 구현하고 있습니다.

OptionSet은 첨부형 매크로이므로 구조체와 클래스의 이름처럼 대문자 카멜 케이스를 사용합니다. 독립형 매크로는 변수와 함수의 이름처럼 소문자 카멜 케이스를 사용하죠.

매크로 선언에서는 매크로의 roles를 정의합니다. 즉, 해당 매크로를 호출할 수 있는 소스 코드의 위치와 매크로가 생성할 수 있는 코드의 종류를 정의하는 것이에요. 모든 매크로에는 하나 이상의 roles가 있으며, 이는 매크로 선언의 시작 부분에 속성으로 작성됩니다. @OptionSet의 선언에서 roles에 대한 속성을 포함한 내용은 다음과 같습니다:

@attached(member)
@attached(extension, conformances: OptionSet)
public macro OptionSet<RawType>() =
        #externalMacro(module: "SwiftMacros", type: "OptionSetMacro")
swift

@attached 속성이 이 선언에서 매크로 역할마다 두 번 나타납니다. 첫 번째 사용인 @attached(member)는 매크로가 적용되는 타입에 새로운 멤버를 추가한다는 것을 나타냅니다. @OptionSet 매크로는 OptionSet 프로토콜에서 요구하는 init(rawValue:) 이니셜라이저와 추가적인 멤버들을 추가하죠. 두 번째 사용인 @attached(extension, conformances: OptionSet)@OptionSetOptionSet 프로토콜에 대한 준수성을 추가한다는 것을 알려줍니다. @OptionSet 매크로는 매크로가 적용되는 타입을 확장하여 OptionSet 프로토콜에 대한 준수성을 추가하는 거에요.

독립형 매크로의 경우에는 @freestanding 속성을 작성하여 해당 매크로의 역할을 지정합니다:

@freestanding(expression)
public macro line<T: ExpressibleByIntegerLiteral>() -> T =
        /* ... 매크로 구현의 위치 ... */
swift

위의 #line 매크로는 expression 역할을 가지고 있습니다. expression 매크로는 값을 생성하거나 경고 생성과 같은 컴파일 시점 동작을 수행합니다.

매크로의 역할 외에도 매크로 선언은 매크로가 생성하는 심볼의 이름에 대한 정보를 제공합니다. 매크로 선언에서 이름의 목록을 제공하면, 해당 매크로는 그 이름을 사용하는 선언만 생성하도록 보장됩니다. 이는 생성된 코드를 이해하고 디버깅하는 데 도움이 됩니다. 다음은 @OptionSet의 전체 선언이에요:

@attached(member, names: named(RawValue), named(rawValue),
        named(`init`), arbitrary)
@attached(extension, conformances: OptionSet)
public macro OptionSet<RawType>() =
        #externalMacro(module: "SwiftMacros", type: "OptionSetMacro")
swift

위의 선언에서 @attached(member) 매크로는 named: 레이블 뒤에 @OptionSet 매크로가 생성하는 각 심볼에 대한 인수를 포함하고 있습니다. 이 매크로는 RawValue, rawValue, init이라는 이름의 심볼에 대한 선언을 추가합니다. 이 이름들은 미리 알려져 있으므로 매크로 선언에서 명시적으로 나열되어 있죠.

또한 매크로 선언에는 이름 목록 뒤에 arbitrary가 포함되어 있어서, 매크로 사용 시점에 이름을 알 수 없는 선언도 생성할 수 있습니다. 예를 들어, 위의 SundaeToppings@OptionSet 매크로를 적용하면, nuts, cherry, fudge에 해당하는 타입 속성이 생성됩니다.

더 자세한 정보와 매크로 roles의 전체 목록은 Attributes첨부형독립형를 참조하시면 됩니다.

lecture image

이 그림은 매크로 선언에 포함되는 내용을 간단히 도식화한 것입니다. 매크로의 이름, 매개변수, 사용 가능한 위치, 생성하는 코드의 종류 등이 매크로 선언에 명시됩니다.

매크로를 사용하면 반복적인 코드를 간결하게 작성할 수 있고, 코드의 가독성과 유지보수성을 높일 수 있습니다. 하지만 매크로를 남용하면 오히려 코드를 이해하기 어려워질 수 있으니 적절히 사용하는 것이 중요하겠죠? 매크로의 선언과 구현을 잘 이해하고, 필요한 곳에 적절히 활용한다면 Swift 코드 작성에 큰 도움이 될 거예요!