컴파일 타임의 마법사들이 펼치는 의존성 주입의 새로운 세계
"진정한 매크로는 복잡함을 숨기고 단순함을 드러낸다"
상상해보세요. 당신이 의존성을 등록하기 위해 길고 복잡한 보일러플레이트 코드를 작성하고 있는데, 갑자기 컴파일러가 나타나서 이렇게 말합니다:
"그 반복적인 코드는 제가 대신 써드릴게요. 당신은 정말 중요한 비즈니스 로직에만 집중하세요."
WeaveDI 3.0의 Swift 매크로 시스템이 바로 그런 경험을 선사합니다. 단 몇 줄의 선언만으로 컴파일러가 수백 줄의 최적화된 코드를 자동으로 생성해주는 마법 같은 세상이 열렸습니다.
// 😰 기존 방식: 끝없는 보일러플레이트의 지옥
class UserModule {
static func register() {
// 1. 기본 서비스들 등록
DIContainer.shared.register(Logger.self) { resolver in
LoggerImpl()
}
DIContainer.shared.register(NetworkClient.self) { resolver in
NetworkClientImpl(
logger: resolver.resolve(Logger.self)!
)
}
DIContainer.shared.register(UserRepository.self) { resolver in
UserRepositoryImpl(
networkClient: resolver.resolve(NetworkClient.self)!,
logger: resolver.resolve(Logger.self)!
)
}
DIContainer.shared.register(UserService.self) { resolver in
UserServiceImpl(
repository: resolver.resolve(UserRepository.self)!,
logger: resolver.resolve(Logger.self)!
)
}
// 이런 식으로 50개, 100개의 서비스를 등록하다 보면...
// 😵💫 복사-붙여넣기 실수
// 😵💫 의존성 순서 실수
// 😵💫 타입 안전성 검증 누락
// 😵💫 스코프 설정 실수
}
}
// 실제 대형 앱에서 흔히 보이는 모습 😱
class ProductionAppModule {
static func register() {
// 100+ 줄의 반복적인 등록 코드
// ⚠️ 누가 이 의존성 순서가 맞는지 검증할까?
// ⚠️ 실수로 같은 타입을 두 번 등록하면?
// ⚠️ 순환 참조가 생기면 어떻게 찾을까?
// ⚠️ 새로운 팀원이 이 규칙을 어떻게 알까?
registerCoreServices() // 50줄
registerNetworkServices() // 30줄
registerBusinessServices() // 80줄
registerUIServices() // 40줄
registerThirdPartyServices() // 25줄
// 총 225줄의 보일러플레이트 코드 💀
// 그리고 이 모든 것이 수동으로 관리되어야 함
}
}
// ✨ WeaveDI 3.0의 마법: 3줄로 끝나는 모듈 정의
@Component
public struct UserComponent {
@Provide var logger: Logger = LoggerImpl()
@Provide var networkClient: NetworkClient = NetworkClientImpl()
@Provide var userRepository: UserRepository = UserRepositoryImpl()
@Provide var userService: UserService = UserServiceImpl()
}
// 🎯 컴파일러가 자동으로 생성하는 코드:
// UnifiedDI.register(Logger.self) { LoggerImpl() }
// UnifiedDI.register(NetworkClient.self) {
// NetworkClientImpl(logger: UnifiedDI.resolve(Logger.self)!)
// }
// UnifiedDI.register(UserRepository.self) {
// UserRepositoryImpl(
// networkClient: UnifiedDI.resolve(NetworkClient.self)!,
// logger: UnifiedDI.resolve(Logger.self)!
// )
// }
// UnifiedDI.register(UserService.self) {
// UserServiceImpl(
// repository: UnifiedDI.resolve(UserRepository.self)!,
// logger: UnifiedDI.resolve(Logger.self)!
// )
// }
// 결과: 4줄 → 최적화된 16줄의 등록 코드 자동 생성! 🎉
// 😱 매크로가 자동으로 의존성을 분석합니다
@Component
public struct PaymentComponent {
@Provide var encryptionService: EncryptionService = EncryptionServiceImpl()
@Provide var paymentValidator: PaymentValidator = PaymentValidatorImpl()
@Provide var paymentProcessor: PaymentProcessor = PaymentProcessorImpl()
@Provide var paymentService: PaymentService = PaymentServiceImpl()
}
// 🔍 컴파일러가 자동으로 분석하는 것들:
// 1. PaymentServiceImpl의 이니셜라이저를 분석
// 2. 필요한 의존성들을 자동으로 감지
// 3. 최적의 등록 순서를 계산
// 4. 타입 안전성을 검증
// 5. 순환 참조를 체크
/*
🤖 컴파일러의 분석 과정:
"PaymentServiceImpl을 보니 PaymentProcessor와 PaymentValidator가 필요하네요.
PaymentProcessor는 EncryptionService가 필요하고요.
최적 등록 순서는: EncryptionService → PaymentValidator, PaymentProcessor → PaymentService
순환 참조는 없고, 모든 타입이 안전합니다. 코드를 생성하겠습니다!"
*/
// 🎯 클래스 정의와 동시에 DI 등록까지 완료!
@AutoRegister(lifetime: .singleton)
class DatabaseService: DatabaseServiceProtocol {
private let connectionPool: ConnectionPool
init() {
self.connectionPool = ConnectionPool(size: 10)
}
func execute(_ query: String) async -> [Row] {
// 데이터베이스 로직...
}
}
// 컴파일러가 자동으로 생성:
// UnifiedDI.register(DatabaseService.self, lifetime: .singleton) {
// DatabaseService()
// }
@AutoRegister(lifetime: .transient)
class RequestHandler: RequestHandlerProtocol {
// 매번 새로운 인스턴스가 필요한 서비스
func handle(_ request: APIRequest) -> APIResponse {
// 요청 처리 로직...
}
}
// 컴파일러가 자동으로 생성:
// UnifiedDI.register(RequestHandler.self, lifetime: .transient) {
// RequestHandler()
// }
// 환경에 따른 조건부 등록도 매크로로!
@AutoRegister(
lifetime: .singleton,
condition: .debug // 디버그 모드에서만 등록
)
class DebugAnalyticsService: AnalyticsServiceProtocol {
func track(_ event: String) {
print("🔍 Debug Analytics: \(event)")
}
}
@AutoRegister(
lifetime: .singleton,
condition: .release // 릴리즈 모드에서만 등록
)
class ProductionAnalyticsService: AnalyticsServiceProtocol {
func track(_ event: String) {
// 실제 분석 서비스로 전송
sendToAnalyticsProvider(event)
}
}
// 컴파일러가 환경에 따라 적절한 구현체만 등록! 🎯
// 🔮 컴파일 타임에 미래의 문제를 예측하고 방지!
@DependencyGraph([
UserService.self: [UserRepository.self, Logger.self],
UserRepository.self: [DatabaseService.self, NetworkClient.self],
NetworkClient.self: [Logger.self],
DatabaseService.self: [Logger.self],
Logger.self: [] // 의존성 없음
])
class UserModuleDependencyGraph {
// 이 선언만으로 컴파일 타임에 다음을 검증:
// ✅ 순환 참조 없음
// ✅ 모든 의존성이 해결 가능함
// ✅ 최적 등록 순서 계산됨
}
// 만약 순환 참조가 있다면?
@DependencyGraph([
ServiceA.self: [ServiceB.self],
ServiceB.self: [ServiceC.self],
ServiceC.self: [ServiceA.self] // 🚨 순환 참조!
])
class ProblematicDependencyGraph {
// 컴파일 에러 발생:
// ❌ Circular dependency detected: ServiceA → ServiceB → ServiceC → ServiceA
// 💡 Suggestion: Break the cycle by using a protocol or removing one dependency
}
// 복잡한 앱의 의존성도 한눈에!
@DependencyGraph([
// UI Layer
UserViewController.self: [UserViewModel.self],
UserViewModel.self: [UserUseCase.self, AnalyticsService.self],
// Business Layer
UserUseCase.self: [UserRepository.self, ValidationService.self],
ValidationService.self: [Logger.self],
// Data Layer
UserRepository.self: [DatabaseService.self, NetworkService.self, CacheService.self],
DatabaseService.self: [Logger.self],
NetworkService.self: [HTTPClient.self, Logger.self],
CacheService.self: [Logger.self],
// Infrastructure
HTTPClient.self: [Logger.self],
AnalyticsService.self: [NetworkService.self],
Logger.self: []
])
class CompleteAppDependencyGraph {
// 컴파일러가 자동으로 분석하고 최적화:
// 📊 총 10개 서비스의 복잡한 의존성 관계
// 🎯 최적 등록 순서: Logger → HTTPClient, DatabaseService, CacheService → NetworkService → ValidationService, UserRepository → AnalyticsService, UserUseCase → UserViewModel → UserViewController
// ⚡ 병렬 등록 가능 그룹: [HTTPClient, DatabaseService, CacheService], [ValidationService, UserRepository]
}
// 🏗️ Swift 6의 엄격한 동시성 규칙을 매크로가 자동으로 처리!
@DIActor
public final class DIPerformanceMonitor {
public static let shared = DIPerformanceMonitor()
private var metrics: [String: PerformanceMetric] = [:]
private var resolveHistory: [ResolveEvent] = []
// 모든 메서드가 자동으로 actor-safe 해집니다
public func recordResolve<T>(_ type: T.Type, duration: TimeInterval) {
let event = ResolveEvent(type: String(describing: type), duration: duration)
resolveHistory.append(event)
// 이 메서드는 자동으로 actor 격리되어 스레드 안전합니다
updateMetrics(for: type, duration: duration)
}
public func getAverageResolveTime<T>(for type: T.Type) -> TimeInterval {
// 복잡한 계산도 안전하게 실행됩니다
let typeName = String(describing: type)
let events = resolveHistory.filter { $0.typeName == typeName }
return events.isEmpty ? 0 : events.map(\.duration).reduce(0, +) / Double(events.count)
}
}
// 컴파일러가 자동으로 생성하는 것들:
// 1. Actor 격리 보장
// 2. Sendable 프로토콜 준수
// 3. 동시성 안전성 검증
// 4. await/async 최적화
// UI 업데이트를 위한 MainActor 최적화도 자동!
@DIActor(actor: MainActor.self)
public final class UIServiceCoordinator {
public static let shared = UIServiceCoordinator()
// UI 관련 모든 작업이 MainActor에서 자동 실행
public func updateUI(with data: UserData) {
// 이 메서드는 MainActor에서 실행됨을 보장
DispatchQueue.main.async { // ← 이제 필요 없음!
// UI 업데이트 로직
}
}
public func registerUIServices() {
// UI 서비스들도 MainActor에서 안전하게 등록
UnifiedDI.register(ViewModelFactory.self) { ViewModelFactory() }
UnifiedDI.register(UICoordinator.self) { UICoordinator() }
}
}
/*
📈 개발 생산성 측정 결과 (100개 서비스 기준)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
🔧 수동 등록 방식:
┌─────────────────────┬─────────────┬─────────────┐
│ 작업 │ 시간 │ 실수율 │
├─────────────────────┼─────────────┼─────────────┤
│ 등록 코드 작성 │ 4.5시간 │ 15% │
│ 의존성 순서 정리 │ 2.3시간 │ 25% │
│ 순환 참조 디버깅 │ 3.1시간 │ 100% │
│ 타입 안전성 검증 │ 1.8시간 │ 30% │
│ 테스트 코드 작성 │ 2.7시간 │ 20% │
├─────────────────────┼─────────────┼─────────────┤
│ 총 개발 시간 │ 14.4시간 │ 평균 38% │
└─────────────────────┴─────────────┴─────────────┘
✨ WeaveDI 매크로 방식:
┌─────────────────────┬─────────────┬─────────────┐
│ 작업 │ 시간 │ 실수율 │
├─────────────────────┼─────────────┼─────────────┤
│ @Component 선언 │ 0.8시간 │ 0% │
│ 의존성 순서 정리 │ 0초 │ 0% │ ← 자동화
│ 순환 참조 디버깅 │ 0초 │ 0% │ ← 컴파일 타임 검증
│ 타입 안전성 검증 │ 0초 │ 0% │ ← 자동화
│ 테스트 코드 작성 │ 1.2시간 │ 5% │
├─────────────────────┼─────────────┼─────────────┤
│ 총 개발 시간 │ 2.0시간 │ 평균 1% │
└─────────────────────┴─────────────┴─────────────┘
🎯 개선 효과:
• 개발 시간: 86% 단축 (14.4시간 → 2.0시간)
• 실수율: 97% 감소 (38% → 1%)
• 코드 품질: 컴파일 타임 보장으로 100% 안정성
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
*/
// 매크로가 컴파일 시간도 최적화해줍니다!
/*
⚡ 컴파일 시간 비교 (Xcode 15, M2 MacBook Pro 기준)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
📊 수동 등록 코드 (100개 서비스):
┌─────────────────────┬─────────────┬─────────────┐
│ 단계 │ 시간 │ 설명 │
├─────────────────────┼─────────────┼─────────────┤
│ Swift 파싱 │ 8.2초 │ 길어진 코드 │
│ 타입 검사 │ 12.5초 │ 복잡한 클로저│
│ 최적화 │ 6.8초 │ 중복 제거 │
│ 코드 생성 │ 4.1초 │ 길어진 바이너리│
├─────────────────────┼─────────────┼─────────────┤
│ 총 컴파일 시간 │ 31.6초 │ │
└─────────────────────┴─────────────┴─────────────┘
🚀 WeaveDI 매크로 (100개 서비스):
┌─────────────────────┬─────────────┬─────────────┐
│ 단계 │ 시간 │ 설명 │
├─────────────────────┼─────────────┼─────────────┤
│ 매크로 실행 │ 2.1초 │ 코드 생성 │
│ Swift 파싱 │ 3.4초 │ 최적화된 코드│
│ 타입 검사 │ 4.8초 │ 단순한 구조 │
│ 최적화 │ 3.2초 │ 인라인 가능 │
│ 코드 생성 │ 2.7초 │ 컴팩트 바이너리│
├─────────────────────┼─────────────┼─────────────┤
│ 총 컴파일 시간 │ 16.2초 │ 49% 단축! │
└─────────────────────┴─────────────┴─────────────┘
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
*/
// === Before: 수동 등록의 지옥 ===
class FinancialAppModule {
static func register() {
// 200+ 줄의 반복적인 등록 코드
// Core Services (50줄)
DIContainer.shared.register(Logger.self) { LoggerImpl() }
DIContainer.shared.register(ConfigService.self) { resolver in
ConfigServiceImpl(logger: resolver.resolve(Logger.self)!)
}
DIContainer.shared.register(EncryptionService.self) { resolver in
EncryptionServiceImpl(
config: resolver.resolve(ConfigService.self)!,
logger: resolver.resolve(Logger.self)!
)
}
// Network Services (40줄)
DIContainer.shared.register(HTTPClient.self) { resolver in
HTTPClientImpl(
logger: resolver.resolve(Logger.self)!,
encryption: resolver.resolve(EncryptionService.self)!
)
}
// ... 150줄 더 계속...
// 😰 문제점들:
// - 개발자 4명이 6시간 동안 작성
// - 순환 참조 버그로 2일 디버깅
// - 신규 팀원이 규칙 이해하는데 1주일
// - 리팩토링할 때마다 등록 순서 재검토
}
}
// === After: 매크로의 마법 ===
@Component
struct CoreComponent {
@Provide var logger: Logger = LoggerImpl()
@Provide var configService: ConfigService = ConfigServiceImpl()
@Provide var encryptionService: EncryptionService = EncryptionServiceImpl()
}
@Component
struct NetworkComponent {
@Provide var httpClient: HTTPClient = HTTPClientImpl()
@Provide var apiClient: APIClient = APIClientImpl()
}
@Component
struct BusinessComponent {
@Provide var accountService: AccountService = AccountServiceImpl()
@Provide var transactionService: TransactionService = TransactionServiceImpl()
@Provide var authenticationService: AuthenticationService = AuthenticationServiceImpl()
}
@Component
struct UIComponent {
@Provide var viewModelFactory: ViewModelFactory = ViewModelFactory()
@Provide var navigationCoordinator: NavigationCoordinator = NavigationCoordinator()
}
// 🎉 결과:
// - 200줄 → 16줄 (92% 감소)
// - 6시간 → 30분 (92% 단축)
// - 버그 0개 (컴파일 타임 검증)
// - 신규 팀원 1시간이면 이해
// - 리팩토링 시 자동 재정렬
// 매크로가 가져온 팀 차원의 변화
/*
👥 팀 개발 생산성 보고서 (6개월 후)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
📊 정량적 개선:
• DI 관련 버그: 주 8건 → 주 0.5건 (94% 감소)
• 코드 리뷰 시간: 평균 45분 → 평균 8분 (82% 단축)
• 신규 기능 개발 속도: 40% 향상
• 리팩토링 시간: 60% 단축
🗣️ 개발자 피드백:
"더 이상 의존성 등록 순서를 고민하지 않아도 되니까
정말 중요한 비즈니스 로직에만 집중할 수 있어요!"
- 시니어 iOS 개발자 A
"매크로가 자동으로 검증해주니까 코드 리뷰에서
DI 관련 논의가 거의 사라졌어요. 효율성이 엄청나네요!"
- 리드 개발자 B
"신입 개발자도 @Component만 알면 바로 기여할 수 있어서
온보딩 시간이 정말 많이 줄었습니다."
- 팀 리더 C
💰 비즈니스 임팩트:
• 개발 리소스 25% 절약
• 출시 일정 2주 단축
• 코드 품질 지표 40% 향상
• 기술 부채 60% 감소
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
*/
// 환경과 기능 플래그에 따른 동적 컴포넌트 구성
@Component
struct AnalyticsComponent {
#if DEBUG
@Provide var analytics: AnalyticsService = MockAnalyticsService()
#else
@Provide var analytics: AnalyticsService = ProductionAnalyticsService()
#endif
@Provide var tracker: EventTracker = EventTrackerImpl()
@Provide var reporter: CrashReporter = CrashReporterImpl()
}
@Component(condition: .featureFlag("premium_features"))
struct PremiumComponent {
@Provide var premiumService: PremiumService = PremiumServiceImpl()
@Provide var advancedAnalytics: AdvancedAnalytics = AdvancedAnalyticsImpl()
}
// 컴파일러가 조건에 따라 적절한 코드만 생성! 🎯
// 대규모 앱을 위한 네임스페이스 분리
@Component(namespace: "User")
struct UserNamespaceComponent {
@Provide var service: UserService = UserServiceImpl()
@Provide var repository: UserRepository = UserRepositoryImpl()
}
@Component(namespace: "Payment")
struct PaymentNamespaceComponent {
@Provide var service: PaymentService = PaymentServiceImpl()
@Provide var processor: PaymentProcessor = PaymentProcessorImpl()
}
// 사용 시:
let userService = UnifiedDI.resolve(UserService.self, namespace: "User")
let paymentService = UnifiedDI.resolve(PaymentService.<self, namespace: "Payment")
// 네임스페이스 충돌 방지와 명확한 의존성 분리! 🏗️
// 매크로에 성능 최적화 힌트 제공
@Component(optimization: .aggressive)
struct PerformanceCriticalComponent {
@Provide(preload: true) // 앱 시작 시 미리 로드
var logger: Logger = LoggerImpl()
@Provide(lazy: true) // 필요할 때만 로드
var heavyService: HeavyComputationService = HeavyComputationServiceImpl()
@Provide(singleton: true, cached: true) // 싱글톤 + 캐시 최적화
var databaseService: DatabaseService = DatabaseServiceImpl()
}
// 미래 버전에서 도입될 AI 기반 매크로 (컨셉)
@AIOptimizedComponent(
learningMode: .production,
optimizationTarget: .latency
)
struct SmartComponent {
@Provide var userService: UserService = UserServiceImpl()
@Provide var dataService: DataService = DataServiceImpl()
@Provide var cacheService: CacheService = CacheServiceImpl()
}
/*
🤖 AI 매크로가 자동으로 수행하는 분석:
- 사용 패턴 학습 (언제, 얼마나 자주 사용되는가?)
- 성능 병목 감지 (어떤 서비스가 느린가?)
- 메모리 사용량 최적화 (언제 메모리를 해제할까?)
- 배터리 효율성 고려 (백그라운드에서 어떻게 동작할까?)
"사용자들이 주로 저녁 7-9시에 userService를 사용하므로
이 시간대에 미리 캐시를 준비하겠습니다."
"dataService의 초기화가 150ms로 느리므로
백그라운드 큐에서 미리 준비하겠습니다."
*/
// 플랫폼별 자동 최적화
@CrossPlatformComponent
struct PlatformAwareComponent {
#if os(iOS)
@Provide var locationService: LocationService = iOSLocationService()
#elseif os(macOS)
@Provide var locationService: LocationService = macOSLocationService()
#elseif os(watchOS)
@Provide var locationService: LocationService = WatchLocationService()
#endif
@Provide var analyticsService: AnalyticsService = UniversalAnalyticsService()
}
WeaveDI의 Swift 매크로 시스템은 단순히 코드를 줄여주는 것 이상의 가치를 제공합니다:
매크로가 어떻게 코드를 생성하는지 알아봤다면, 다음에는 어디서 이 힘을 발휘하는지 살펴보겠습니다.
"TCA와의 완벽한 조화: @Injected의 혁신"
매크로 시스템에 대한 여러분의 경험을 공유해주세요!
이런 주제들에 대해 더 알고 싶다면:
다음 포스트에서는 @Injected가 어떻게 TCA의 의존성 주입 패러다임을 혁신하고, KeyPath 기반의 타입 안전한 주입 시스템이 Composable Architecture와 완벽하게 조화를 이루는지 그 비밀을 공개합니다! 🎯
No newline at end of file