YunDaeHyeon
← Lab

Swift 의존성 조립기(Assembler)

스터디2025.06.01
  • Swift
  • KeyChain
  • Keychain

단일 AppAssembler 구조

import Foundation

// MARK: - Assembler Protocol
protocol Assembler {
    func assemble(container: DIContainer)
}

// MARK: - App Assembler (단일 Assembler)
final class AppAssembler: Assembler {
    func assemble(container: DIContainer) {
        // MARK: - Network
        container.register(RequesterProtocol.self, lifetime: .singleton) {
            Requester(configuration: .default)
        }
        
        // MARK: - Keychain
        container.register(KeychainManagerProtocol.self, lifetime: .singleton) {
            KeychainManager()
        }
        
        // MARK: - Auth
        container.register(AuthProviderProtocol.self, lifetime: .singleton) {
            AuthProvider(keychainManager: container.resolve(KeychainManagerProtocol.self))
        }
        
        // MARK: - Login
        container.register(LoginRepositoryProtocol.self, lifetime: .singleton) {
            LoginRepository(
                requester: container.resolve(RequesterProtocol.self),
                keychainManager: container.resolve(KeychainManagerProtocol.self)
            )
        }
        
        container.register(LoginUseCaseProtocol.self, lifetime: .singleton) {
            LoginUseCase(repository: container.resolve(LoginRepositoryProtocol.self))
        }
        
        container.register(LoginViewModel.self, lifetime: .transient) {
            LoginViewModel(
                loginUseCase: container.resolve(LoginUseCaseProtocol.self),
                authProvider: container.resolve(AuthProviderProtocol.self)
            )
        }
        
        // MARK: - IssueList
        container.register(IssueListViewModel.self, lifetime: .transient) {
            IssueListViewModel(authProvider: container.resolve(AuthProviderProtocol.self))
        }
    }
}

// MARK: - 적용된 IssueTrackerApp
@main
struct IssueTrackerApp: App {
    private let container = DIContainer.shared
    @StateObject private var authProvider: AuthProvider
    @StateObject private var loginCoordinator = LoginCoordinator()
    
    init() {
        // 1. Assembler 생성 및 의존성 조립
        let assembler = AppAssembler()
        assembler.assemble(container: container)
        
        // 2. Container에서 AuthProvider resolve
        let authProvider: AuthProvider = container.resolve(AuthProviderProtocol.self)
        self._authProvider = StateObject(wrappedValue: authProvider)
    }
    
    var body: some Scene {
        WindowGroup {
            if authProvider.isLoggedIn {
                NavigationStack(path: $loginCoordinator.path) {
                    loginCoordinator.build(.issueListView)
                        .navigationDestination(
                            for: NavigationRoute.self,
                            destination: loginCoordinator.build(_:))
                }
            } else {
                NavigationStack(path: $loginCoordinator.path) {
                    loginCoordinator.build(.loginView)
                        .navigationDestination(
                            for: NavigationRoute.self,
                            destination: loginCoordinator.build(_:))
                }
            }
        }
        .environmentObject(authProvider)
    }
}

// MARK: - 사용 예시 (다른 곳에서 ViewModel resolve)
/*
// LoginView에서
struct LoginView: View {
    @StateObject private var viewModel: LoginViewModel
    
    init() {
        let vm: LoginViewModel = DIContainer.shared.resolve(LoginViewModel.self)
        self._viewModel = StateObject(wrappedValue: vm)
    }
    
    var body: some View {
        // UI 구현
    }
}
*/
  • 장점
  • 파일 하나로 의존성 관리 가능
  • 전체 의존성 흐름 한 눈에 파악 가능
  • 간단한 구조로 초기 설정 용이
  • 단점
  • 프로젝트 규모가 커지면 Assembler 파일이 비대해짐
  • 모듈별 의존성 분리가 안되어 특정 기능만 테스트 불가

모듈 단위 Assembler 구조

import Foundation

// MARK: - Assembler Protocol
protocol Assembler {
    func assemble(container: DIContainer)
}

// MARK: - Auth Assembler
final class AuthAssembler: Assembler {
    func assemble(container: DIContainer) {
        // Keychain Manager
        container.register(KeychainManagerProtocol.self, lifetime: .singleton) {
            KeychainManager()
        }
        
        // Auth Provider
        container.register(AuthProviderProtocol.self, lifetime: .singleton) {
            AuthProvider(keychainManager: container.resolve(KeychainManagerProtocol.self))
        }
    }
}

// MARK: - Network Assembler
final class NetworkAssembler: Assembler {
    func assemble(container: DIContainer) {
        // Requester
        container.register(RequesterProtocol.self, lifetime: .singleton) {
            Requester(configuration: .default)
        }
    }
}

// MARK: - Login Assembler
final class LoginAssembler: Assembler {
    func assemble(container: DIContainer) {
        // Repository
        container.register(LoginRepositoryProtocol.self, lifetime: .singleton) {
            LoginRepository(
                requester: container.resolve(RequesterProtocol.self),
                keychainManager: container.resolve(KeychainManagerProtocol.self)
            )
        }
        
        // UseCase
        container.register(LoginUseCaseProtocol.self, lifetime: .singleton) {
            LoginUseCase(repository: container.resolve(LoginRepositoryProtocol.self))
        }
        
        // ViewModel
        container.register(LoginViewModel.self, lifetime: .transient) {
            LoginViewModel(
                loginUseCase: container.resolve(LoginUseCaseProtocol.self),
                authProvider: container.resolve(AuthProviderProtocol.self)
            )
        }
    }
}

// MARK: - IssueList Assembler
final class IssueListAssembler: Assembler {
    func assemble(container: DIContainer) {
        // ViewModel
        container.register(IssueListViewModel.self, lifetime: .transient) {
            IssueListViewModel(authProvider: container.resolve(AuthProviderProtocol.self))
        }
    }
}

// MARK: - App Assembler (Root)
final class AppAssembler: Assembler {
    private let assemblers: [Assembler]
    
    init(assemblers: [Assembler]) {
        self.assemblers = assemblers
    }
    
    func assemble(container: DIContainer) {
        assemblers.forEach { $0.assemble(container: container) }
    }
}

// MARK: - 적용된 IssueTrackerApp
@main
struct IssueTrackerApp: App {
    private let container = DIContainer.shared
    @StateObject private var authProvider: AuthProvider
    @StateObject private var loginCoordinator = LoginCoordinator()
    
    init() {
        // 1. Assembler 생성 및 의존성 조립
        let assembler = AppAssembler(assemblers: [
            NetworkAssembler(),
            AuthAssembler(),
            LoginAssembler(),
            IssueListAssembler()
        ])
        
        // 2. 모든 의존성 등록
        assembler.assemble(container: container)
        
        // 3. Container에서 AuthProvider resolve
        let authProvider: AuthProvider = container.resolve(AuthProviderProtocol.self)
        self._authProvider = StateObject(wrappedValue: authProvider)
    }
    
    var body: some Scene {
        WindowGroup {
            if authProvider.isLoggedIn {
                NavigationStack(path: $loginCoordinator.path) {
                    loginCoordinator.build(.issueListView)
                        .navigationDestination(
                            for: NavigationRoute.self,
                            destination: loginCoordinator.build(_:))
                }
            } else {
                NavigationStack(path: $loginCoordinator.path) {
                    loginCoordinator.build(.loginView)
                        .navigationDestination(
                            for: NavigationRoute.self,
                            destination: loginCoordinator.build(_:))
                }
            }
        }
        .environmentObject(authProvider)
    }
}

// MARK: - 사용 예시 (다른 곳에서 ViewModel resolve)
/*
// LoginView에서
struct LoginView: View {
    @StateObject private var viewModel: LoginViewModel
    
    init() {
        let vm: LoginViewModel = DIContainer.shared.resolve(LoginViewModel.self)
        self._viewModel = StateObject(wrappedValue: vm)
    }
    
    var body: some View {
        // UI 구현
    }
}
*/
  • Auth, Network, Login, IssueList로 분리된 개별 Assembler들
  • AppleAssembler를 통해 모든 Assembler를 조합하는 루트 어셈블러

link_preview