| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | |||
| 5 | 6 | 7 | 8 | 9 | 10 | 11 |
| 12 | 13 | 14 | 15 | 16 | 17 | 18 |
| 19 | 20 | 21 | 22 | 23 | 24 | 25 |
| 26 | 27 | 28 | 29 | 30 |
- development
- 커스텀 뷰
- 네이버 부캠
- IOS
- Opensource
- SwiftUI
- boostcamp
- rxswift
- notion
- Cocoa Internals
- Swift
- 알고리즘
- World
- 개발
- 단위 테스트
- WWDC
- 디자인패턴
- 부트캠프
- 부스트캠프
- Design Pattern
- OS
- Hello
- Tistory
- Algorithm
- 후기
- ios #swift #uialertcontroller #메서드 스위즐링
- 코코아 인터널스
- Today
- 3
- Total
- 4,299
꿈돌이랜드
[Swift] 모듈화 시 리소스 접근을 해결하는 #bundle매크로 본문
최근 프로젝트의 규모가 커짐에 따라 모듈화(Modularization) 작업을 진행했습니다. 공통으로 사용되는 UI 컴포넌트와 디자인 리소스(Color, Image 등)를 Shared 모듈(Dynamic Framework)로 분리하는 것이 목표였습니다.
하지만, 앱 타겟(App Target)에 있던 리소스를 별도의 프레임워크로 옮긴 후 실행해보니 색상이나 이미지를 불러오지 못하는 문제가 발생했습니다.
이 과정에서 겪은 번들(Bundle) 문제와, Xcode 16 (Swift 5.9+) 환경에서 이를 우아하게 해결해 준 #bundle 매크로에 대해 공유합니다.
1. 문제 상황: 리소스가 왜 nil일까?
보통 우리는 Asset Catalog에 있는 색상을 사용할 때 다음과 같이 호출합니다.
// 일반적인 호출
let myColor = UIColor(named: "MyColor")
앱 타겟 내부에 리소스가 있을 때는 문제없이 잘 동작하던 코드입니다. 하지만 리소스를 리소스 모듈로 옮기자마자 nil을 반환하기 시작했습니다. 원인은 UIColor(named:)의 기본 동작 방식에 있습니다.
// UIColor(named:)의 실제 동작
UIColor(named: "MyColor", in: .main, compatibleWith: nil)
Bundle.main은 현재 실행 중인 App의 번들을 의미합니다. 하지만 제가 분리한 리소스는 Shared 프레임워크의 번들 내부에 존재하기 때문에, 앱 번들에서 아무리 찾아도 리소스를 발견할 수 없었던 것입니다.
2. 기존의 해결 방식 (The Old Way)
이 문제를 해결하려면 리소스가 위치한 정확한 Bundle을 명시해 주어야 합니다. 하지만 기존 방식은 환경에 따라 코드가 달라지는 불편함이 있었습니다.
Framework: Bundle(for: MyClass.self)
Swift Package Manager (SPM): Bundle.module
만약 코드를 SPM과 프레임워크 환경 양쪽에서 공유해야 한다면, 전처리기를 사용하거나 별도의 BundleAccessor를 만들어야 했습니다.
3. 해결책: #bundle 매크로
Xcode 16 환경에서 이 문제를 가장 깔끔하게 해결할 수 있는 방법은 Swift의 공식 표현식 매크로인 #bundle을 사용하는 것입니다.
#bundle은 컴파일 타임에 현재 코드가 속한 타겟에 가장 적합한 번들을 자동으로 반환해 줍니다.
// 리소스 모듈 내부
// [Before] 번들 위치를 명시하기 위해 고민이 필요함
let color = UIColor(named: "MyColor", in: Bundle(for: CurrentClass.self), compatibleWith: nil)
// [After] #bundle 하나로 해결
let color = UIColor(named: "MyColor", in: #bundle, compatibleWith: nil)
#bundle의 동작 원리
이 매크로는 코드가 컴파일되는 위치에 따라 알맞은 번들 접근자로 변환됩니다.
App Target Bundle.main
Framework Bundle(for: $self_type.self)
Swift Package Bundle.module
App Extension Extension 번들
4. 활용 범위
#bundle은 단순히 UIColor뿐만 아니라 번들을 인자로 받는 모든 API에서 활용 가능합니다. 특히 로컬라이징(String Catalog) 처리에 유용합니다.
label.text = String(
localized: "Game Over.",
bundle: #bundle, // 고민 없이 #bundle 사용
comment: "Text for game over banner."
)
5. 도입 시 장점
환경 독립적 코드: App, Framework, SPM 어떤 환경으로 코드를 이동해도 수정할 필요가 없습니다.
안전성: 다이나믹 프레임워크나 Static 라이브러리 등 복잡한 링크 구조에서도 리소스 위치를 안전하게 찾습니다.
Tuist/SPM 설정 간소화:
Tuist 사용 시 리소스 접근을 위해 disableBundleAccessor 옵션을 끄거나 별도의 BundleAccessor를 생성하곤 했습니다.
이제는 네이티브 매크로인 #bundle이 그 역할을 완벽히 대체하므로 불필요한 보일러플레이트 코드를 줄일 수 있습니다.
백포트 지원: 최신 문법이지만 iOS 15, macOS 12 이상 타겟이라면 문제없이 동작합니다.
마무리
모듈화를 진행하다 보면 "리소스 번들 위치" 문제는 필연적으로 마주치게 됩니다. 이제 복잡한 번들 분기 처리나 커스텀 헬퍼 클래스 대신, 표준 매크로인 #bundle을 사용하여 더 간결하고 명확한 코드를 작성해 보시길 바랍니다.
| https://developer.apple.com/documentation/foundation/bundle()
'Programming > iOS' 카테고리의 다른 글
| Swift Concurrency의 함정사항 정리 (0) | 2025.04.02 |
|---|---|
| TCA 아키텍처에서 IdentifiedArray를 사용해야 하는 이유 (0) | 2025.03.21 |
| 메서드 스위즐링을 적용하여 실수로부터 벗어나기 (0) | 2025.03.17 |
| [번역] SwiftNIO Readme Conceptual Overview (0) | 2024.01.16 |
| [iOS] Core Animation Basic (0) | 2023.09.30 |