🔥 첨부형 매크로

372자
5분

Swift에서 매크로(Macro)는 선언에 코드를 추가하는 강력한 기능을 제공합니다. 첨부형 매크로(Attached Macro)라고 불리는 이 매크로는 선언 앞에 @ 기호와 함께 매크로 이름을 작성하고, 필요한 경우 괄호 안에 인자를 넣어 호출할 수 있어요.

첨부형 매크로는 자신이 붙어있는 선언을 수정하는데, 이는 새로운 메서드를 정의하거나 프로토콜 준수를 추가하는 등의 작업을 통해 이루어집니다.

예를 들어, 다음은 매크로를 사용하지 않은 코드입니다:

swift
struct SundaeToppings: OptionSet {
    let rawValue: Int
    static let nuts = SundaeToppings(rawValue: 1 << 0)
    static let cherry = SundaeToppings(rawValue: 1 << 1)
    static let fudge = SundaeToppings(rawValue: 1 << 2)
}
swift
struct SundaeToppings: OptionSet {
    let rawValue: Int
    static let nuts = SundaeToppings(rawValue: 1 << 0)
    static let cherry = SundaeToppings(rawValue: 1 << 1)
    static let fudge = SundaeToppings(rawValue: 1 << 2)
}

이 코드에서 SundaeToppings 옵션 셋의 각 옵션은 이니셜라이저를 호출하는데, 이는 반복적이고 수동적이에요. 새로운 옵션을 추가할 때 실수를 하기 쉽겠죠. 예를 들어 줄 끝에 잘못된 숫자를 입력할 수 있습니다.

이제 매크로를 사용한 버전을 살펴봅시다:

swift
@OptionSet<Int>
struct SundaeToppings {
    private enum Options: Int {
        case nuts
        case cherry
        case fudge
    }
}
swift
@OptionSet<Int>
struct SundaeToppings {
    private enum Options: Int {
        case nuts
        case cherry
        case fudge
    }
}

이 버전의 SundaeToppings@OptionSet 매크로를 호출합니다. 매크로는 private 열거형의 case 목록을 읽고, 각 옵션에 대한 상수 목록을 생성하며, OptionSet 프로토콜에 대한 준수를 추가해요.

비교를 위해 @OptionSet 매크로를 펼친 버전을 살펴보겠습니다. 이 코드는 직접 작성하지 않으며, Swift에게 매크로의 확장을 명시적으로 요청했을 때만 볼 수 있어요.

swift
struct SundaeToppings {
    private enum Options: Int {
        case nuts
        case cherry
        case fudge
    }
 
    // rawValue 타입 별칭 선언
    typealias RawValue = Int
    // rawValue 속성 선언
    var rawValue: RawValue
    // 기본 이니셜라이저. rawValue를 0으로 초기화
    init() { self.rawValue = 0 }
    // rawValue를 인자로 받는 이니셜라이저
    init(rawValue: RawValue) { self.rawValue = rawValue }
    // nuts 정적 속성. Options.nuts.rawValue를 shift 연산한 값으로 인스턴스 생성
    static let nuts: Self = Self(rawValue: 1 << Options.nuts.rawValue)
    // cherry 정적 속성. Options.cherry.rawValue를 shift 연산한 값으로 인스턴스 생성
    static let cherry: Self = Self(rawValue: 1 << Options.cherry.rawValue)
    // fudge 정적 속성. Options.fudge.rawValue를 shift 연산한 값으로 인스턴스 생성
    static let fudge: Self = Self(rawValue: 1 << Options.fudge.rawValue)
}
// SundaeToppings를 OptionSet 프로토콜 준수하도록 확장
extension SundaeToppings: OptionSet { }
swift
struct SundaeToppings {
    private enum Options: Int {
        case nuts
        case cherry
        case fudge
    }
 
    // rawValue 타입 별칭 선언
    typealias RawValue = Int
    // rawValue 속성 선언
    var rawValue: RawValue
    // 기본 이니셜라이저. rawValue를 0으로 초기화
    init() { self.rawValue = 0 }
    // rawValue를 인자로 받는 이니셜라이저
    init(rawValue: RawValue) { self.rawValue = rawValue }
    // nuts 정적 속성. Options.nuts.rawValue를 shift 연산한 값으로 인스턴스 생성
    static let nuts: Self = Self(rawValue: 1 << Options.nuts.rawValue)
    // cherry 정적 속성. Options.cherry.rawValue를 shift 연산한 값으로 인스턴스 생성
    static let cherry: Self = Self(rawValue: 1 << Options.cherry.rawValue)
    // fudge 정적 속성. Options.fudge.rawValue를 shift 연산한 값으로 인스턴스 생성
    static let fudge: Self = Self(rawValue: 1 << Options.fudge.rawValue)
}
// SundaeToppings를 OptionSet 프로토콜 준수하도록 확장
extension SundaeToppings: OptionSet { }

private 열거형 이후의 모든 코드는 @OptionSet 매크로에서 비롯됩니다. 매크로를 사용하여 모든 정적 변수를 생성하는 버전의 SundaeToppings는 이전의 수동으로 코딩된 버전보다 읽기 쉽고 유지 관리하기 쉽습니다.

이처럼 첨부형 매크로는 선언에 코드를 자동으로 생성하여 추가함으로써 코드를 간결하고 표현력 있게 작성할 수 있도록 도와줍니다. 개발자는 반복적이고 실수하기 쉬운 작업을 매크로에 맡기고, 좀 더 높은 수준의 추상화에 집중할 수 있게 되는 거죠.

lecture image

YouTube 영상

채널 보기
Pro펑터, 입력과 출력을 동시에 다루는 펑터 | 프로그래머를 위한 카테고리 이론
NestJS 파이프가 뭔가요? 컨트롤러를 보호하는 방법 | NestJS 가이드
NestJS 가드, 바이딩과 스코프 | NestJS 가이드
함수 타입과 Hom-Set 이해하기 | 프로그래머를 위한 카테고리 이론
존 매카시가 들려주는 인공지능의 탄생 이야기
NestJS 역할 기반 접근 권한 부여 - Guard, Reflector | NestJS 가이드
클로드 섀넌이 들려주는 정보 이론 이야기
바이펑터란? | 프로그래머를 위한 카테고리 이론