프로퍼티 래퍼 편 말미에서 “골뱅이가 붙었다고 다 래퍼는 아니다”라는 복선을 깔았습니다. @Observable이 그 주인공이에요.
이전 글 Swift 심화 #6에서 이어지는 내용입니다.
생김새는 래퍼인데 정체는 매크로입니다. Xcode에서 #Preview를 쓸 때도, SwiftData의 @Model을 쓸 때도 우리는 이미 매크로의 소비자입니다.
심화 시리즈, 이번 편은 Swift 5.9에 들어온 매크로가 무엇이고 어떤 문제를 푸는지, 또 어떤 비용을 청구하는지 정리합니다. SE-0382: Expression Macros
매크로가 푸는 문제 — 보일러플레이트의 마지막 보루
Swift는 반복 코드를 줄이는 장치를 꾸준히 추가해왔습니다. 프로토콜 기본 구현, Codable 자동 합성, 프로퍼티 래퍼.
그런데 이 도구들이 못 건드리는 영역이 남아 있었어요. 타입의 구조를 읽고 그에 맞는 코드를 만들어내는 일입니다.
Observation이 좋은 예입니다. 프로퍼티가 바뀔 때 관찰자에게 알리려면 모든 저장 프로퍼티마다 추적 코드가 필요합니다.
래퍼(@Published)로 하면 프로퍼티마다 골뱅이를 붙여야 했고, 빠뜨린 프로퍼티는 조용히 관찰에서 빠졌죠.
필요한 건 “이 클래스의 저장 프로퍼티 전부에, 각 이름에 맞는 코드를 생성해줘”입니다. 이건 타입 구조를 읽어야 가능한 일이라 래퍼의 능력 밖이에요.
전통적으로 이 영역은 두 가지가 맡았습니다. Objective-C 런타임의 동적 기법(런타임 비용과 불투명함), 아니면 Sourcery 같은 외부 코드 생성 도구(빌드 파이프라인 밖의 별도 관리).
매크로는 이 일을 언어 안으로, 그것도 컴파일 타임으로 가져온 장치입니다.
동작 원리 — 컴파일러에 꽂는 코드 생성 플러그인
Swift 매크로의 정체는 컴파일러 플러그인입니다. C 전처리기의 텍스트 치환과는 근본이 달라요. 동작 순서는 이렇습니다.
- 컴파일러가 소스에서 매크로 사용(#이나 @)을 만나면, 해당 코드의 구문 트리(AST)를 매크로 구현에 넘깁니다.
- 매크로 구현은 별도 프로세스에서 실행되는 Swift 프로그램입니다. SwiftSyntax 라이브러리로 트리를 분석하고 새 코드 조각을 만들어 돌려줍니다.
- 컴파일러가 그 결과를 원래 자리에 접합하고 이후는 평범한 컴파일입니다.
중요한 성질이 이 구조에서 나옵니다.
첫째, 결과물이 검사 가능한 진짜 Swift 코드입니다. Xcode에서 매크로 우클릭 → Expand Macro를 누르면 생성된 코드가 그대로 펼쳐져요.
마법의 커튼을 언제든 걷어볼 수 있다는 건 Progressive Disclosure 편에서 본 “숨기되 감추지 않는다”의 매크로 버전입니다.
둘째, 위생적(hygienic)입니다. 매크로가 만든 변수는 주변 코드의 이름과 충돌하지 않게 관리되고, 매크로는 선언된 역할 범위 밖의 코드를 건드릴 수 없습니다.
C 매크로의 악명 높은 부작용들을 설계 단계에서 차단한 거예요.
셋째, 샌드박스입니다. 매크로 프로세스는 파일·네트워크 접근이 막혀 있어서 컴파일 타임에 임의 코드를 실행하는 보안 구멍이 되지 않습니다.
두 부족 — freestanding(#)과 attached(@)
매크로는 붙는 방식으로 두 갈래입니다.
freestanding 매크로(#)는 그 자리에 코드를 만들어냅니다. #Preview { MyView() }는 프리뷰 등록 구조를 만들고요.
#URL("https://apple.com")류의 매크로는 컴파일 타임에 문자열을 검증하고 언래핑된 URL을 만들어냅니다. 표현식이 놓일 자리에 서는 매크로예요.
attached 매크로(@)는 붙은 선언을 확장합니다. 역할이 세분화되어 있어요.
멤버를 추가하는 member, 프로퍼티에 접근자를 붙이는 accessor, 프로토콜 채택을 더하는 extension 등이 있습니다. 하나의 매크로가 여러 역할을 겸할 수도 있고요.
@Observable이 정확히 이 조합입니다. 클래스의 각 저장 프로퍼티에 관찰 추적 접근자를 붙이고(accessor), 등록 저장소 멤버를 추가합니다(member). 여기에 Observable 프로토콜 채택을 더해요(extension).
프로퍼티마다 골뱅이를 붙이던 @Published 시대의 불편이, 타입에 골뱅이 하나로 줄어든 이유입니다.
이 구분을 알면 라이브러리 문서가 읽힙니다. SwiftData의 @Model, 테스트 프레임워크의 #expect(Swift Testing 편에서 본 그 매크로), Composable Architecture의 @Reducer를 떠올려 보세요.
최근 생태계의 굵직한 API들이 전부 이 두 부족 중 하나입니다.
비용 청구서 — 만들 것인가, 쓰기만 할 것인가
매크로에는 뚜렷한 비용이 있고, 소비자와 생산자의 셈법이 다릅니다.
쓰는 쪽의 비용은 주로 빌드 시간입니다. 매크로 구현은 SwiftSyntax에 의존하는데, 이게 큰 라이브러리예요.
그래서 매크로를 쓰는 패키지를 처음 빌드할 때 SwiftSyntax 컴파일이 통째로 끼어듭니다.
CI의 클린 빌드가 수 분 단위로 늘어나는 사례가 흔하고, 커뮤니티가 프리빌트 바이너리 배포 같은 완화책을 계속 다듬는 중입니다.
그래도 소비자 입장에서는 감수할 만한 비용인 경우가 대부분이에요. Expand Macro로 검사 가능하고, 런타임 비용은 없으니까요.
만드는 쪽의 비용은 훨씬 큽니다. 매크로 작성은 “코드를 다루는 코드”라 난이도 자체가 한 단계 위입니다.
SwiftSyntax API 학습, 매크로 전용 테스트(입력 코드 → 기대 출력 코드 비교), 진단 메시지 설계까지 따라옵니다.
그래서 실무 기준은 보수적으로 잡는 게 맞습니다. 프로토콜 기본 구현·제네릭·래퍼로 풀리는 문제는 그쪽이 먼저고요.
“타입 구조를 읽어야만 하는 반복”이 팀 코드베이스에 광범위하게 존재할 때만 자작 매크로가 후보가 됩니다. YAGNI(You Aren’t Gonna Need It — 필요해질 때까지 만들지 말라는 원칙)의 매크로 버전이죠.
대부분의 팀에게 매크로는 만드는 도구가 아니라, 프레임워크가 주는 걸 정확히 이해하고 쓰는 도구입니다.
정리
- 매크로는 컴파일 타임에 구문 트리를 받아 코드를 생성하는 컴파일러 플러그인입니다(Swift 5.9). 텍스트 치환인 C 매크로와 달리 타입 구조를 읽고 위생적이고 샌드박스에서 돕니다.
- freestanding(#)은 그 자리에 코드를 만들고(#Preview), attached(@)는 붙은 선언을 확장합니다(@Observable, @Model).
- 생성 결과는 Expand Macro로 언제든 검사 가능한 진짜 Swift 코드입니다. 골뱅이가 붙었다고 다 래퍼가 아니니, 이제 문서에서 macro라는 단어를 구분해 읽으면 됩니다.
- 비용은 SwiftSyntax발 빌드 시간(소비자)과 높은 제작 난이도(생산자)입니다. 자작은 “타입 구조를 읽어야만 하는 광범위한 반복”이 확인될 때만. SE-0389: Attached Macros
다음 편은 Swift가 Rust의 영토에서 가져온 개념을 다룹니다. 복사가 금지된 타입 ~Copyable, 그리고 borrowing과 consuming이 여는 소유권의 세계입니다.
출처 및 확인 기준
- SE-0389: Attached Macros — Swift Evolution · 표준·명세 원문 · 확인 2026-08-17 · 근거: attached macro 역할과 생성 가능한 선언
- SE-0382: Expression Macros — Swift Evolution · 표준·명세 원문 · 확인 2026-08-17 · 근거: Swift 5.9 expression macro 모델과 컴파일러 플러그인

![[Swift 심화 #7] Swift 매크로 총정리, @Observable의 정체 대표 이미지](/assets/images/posts/2c0c42f3-4f02-48e1-8e36-2d9f4f1bf9b6/swift-macros-compiler-plugin.jpg)