프로젝트 및 개발적 고민/Project

[Swift/휴식맞쥬] DIContainer

최지철 2024. 9. 3. 16:30
728x90
반응형

https://github.com/Nexters/8potatoes_iOS

 

사건의 시작

현재 휴식맞쥬는 클린아키텍쳐를 기반으로 프로젝트를 진행하고 있습니다. 그렇기 때문에, DI로 네트워크 레이어를 분리하고 객체간의 결합도를 줄이려는 방향으로 개발을 진행하던 중 레이어 및 기능이 분리되면서 초기화 파라미터의 수가 많아졌습니다. 그 결과, 의존성 주입이 복잡해지고, 코드의 가독성이 떨어지게 되었습니다. 그리고 프로젝트의 여러 곳에서 동일한 의존성 객체를 필요로 할 때, 중복으로 객체 생성이 이루어져 불필요한 자원 낭비가 발생했습니다. 이는 메모리 사용량 증가와 성능 저하로 이어질 수 있습니다. 이러한 문제를 마주하여, DIContainer를 통해 해당문제를 해쳐나갈려고합니다. ㅎㅎ

 

기존의 코드 예제

        let usecase = DetailPerfumeUseCase(repository: DetailPerfumeRepository(detailPerfumeService: PerfumeService()))
        let vc = DetailPerfumeViewController(DetailPerfumeViewModel(usecase: usecase, id: id))

문제 해결을 위한 DIContainer 도입

AppDIContainer

final class AppDIContainer {
    func makeSafeAreaDIContainer() -> SafeAreaDIContainer {
        let dependencies = SafeAreaDIContainer.Dependencies(networking: Networking())
        return SafeAreaDIContainer(dependencies: dependencies)
    }
}

DIContainer

final class SafeAreaDIContainer: StartCoordinatorDependencies, MainMapCoordinatorDependencies {
    struct Dependencies {
        let networking: Networking
    }
    
    private let dependencies: Dependencies
    
    init(dependencies: Dependencies) {
        self.dependencies = dependencies
    }
    
    // MARK: - Repositories
    
    func makeLocationInfoRepository() -> LocationInfoRepository {
        return LocationInfoDAO(network: dependencies.networking)
    }
    
    // MARK: - Use Cases
    
    func makeLocationInfoUseCase() -> LocationInfoUseCaseProtocol {
        return LocationInfoUseCase(repository: makeLocationInfoRepository())
    }
    
    // MARK: - Present (Start)
    
    func makeStartReactor(coordinator: StartCoordinatorProtocol) -> StartReactor {
        return StartReactor(usecase: makeLocationInfoUseCase(), coordinator: coordinator)
    }
    
    func makeStartViewController(coordinator: StartCoordinatorProtocol) -> StartViewController {
        return StartViewController(reactor: makeStartReactor(coordinator: coordinator))
    }
    
    // MARK: - Present (Search)
    
    func makeSearchLocationReactor(coordinator: StartCoordinatorProtocol) -> SearchLocationReactor {
        return SearchLocationReactor(usecase: makeLocationInfoUseCase(), coordinator: coordinator)
    }
    
    func makeSearchLocationViewController(coordinator: StartCoordinatorProtocol) -> SearchLocationViewController {
        return SearchLocationViewController(reactor: makeSearchLocationReactor(coordinator: coordinator))
    }
    
    // MARK: - Present (MainMap)
    
    func makeMainMapReactor(coordinator: MainMapCoordinator) -> MainMapReactor {
        return MainMapReactor(usecase: makeLocationInfoUseCase(), coordinator: coordinator)
    }
    
    func makeMainMapViewController(coordinator: MainMapCoordinatorProtocol) -> MainMapViewController {
        return MainMapViewController(reactor: makeMainMapReactor(coordinator: coordinator as! MainMapCoordinator))
    }
    
    // MARK: - Flow Coordinators
    
    func makeStartFlowCoordinator(navigationController: UINavigationController) -> StartCoordinator {
        return StartCoordinator(navigationController: navigationController, dependencies: self)
    }

    func makeMainMapFlowCoordinator(navigationController: UINavigationController) -> MainMapCoordinator {
        return MainMapCoordinator(navigationController: navigationController, dependencies: self)
    }
}

 

전체적인 흐름

  1. AppDIContainer 클래스
    1. 이 클래스는 전체 애플리케이션의 DIContainer 역할을 합니다.
    2. makeSafeAreaDIContainer() 메서드를 통해 SafeAreaDIContainer를 생성합니다. 이 과정에서 SafeAreaDIContainer에 필요한 의존성(Networking 객체)을 주입합니다.
  2. SafeAreaDIContainer 클래스
    1. 이 클래스는 StartCoordinatorMainMapCoordinator에서 사용할 의존성 객체들을 생성하고 관리하는 역할을 합니다.
    2. Dependencies라는 구조체를 사용하여 의존성을 캡슐화하고, 이를 통해 필요한 의존성(Networking 객체)을 관리합니다.
    3. 각 메서드를 통해 특정 기능에 필요한 객체들을 생성하여 반환합니다.

구성 요소별 흐름

  1. Repositories
    1. makeLocationInfoRepository() 메서드는 LocationInfoRepository 객체를 생성합니다. 이 객체는 네트워킹을 통해 위치 정보를 가져오는 역할을 합니다.
    2. 이 메서드는 SafeAreaDIContainer가 가진 dependencies.networking 객체를 사용하여 LocationInfoDAO라는 리포지토리 구현체를 생성합니다.
  2. Use Cases
    1. makeLocationInfoUseCase() 메서드는 LocationInfoUseCaseProtocol을 구현하는 객체를 생성합니다.
    2. 이 객체는 위치 정보를 가져오는 비즈니스 로직을 수행하며, 이 과정에서 LocationInfoRepository를 사용합니다.
  3. Reactor와 ViewController
    1. makeStartReactor(coordinator:)와 makeStartViewController(coordinator:) 메서드는 StartViewController를 초기화할 때 필요한 StartReactor를 생성합니다.
    2. StartReactor는 LocationInfoUseCase와 StartCoordinator를 의존성으로 주입받습니다.
    3. makeSearchLocationReactor(coordinator:)와 makeSearchLocationViewController(coordinator:)도 유사하게 SearchLocationViewController와 관련된 의존성을 주입받아 초기화합니다.
    4. makeMainMapReactor(coordinator:)와 makeMainMapViewController(coordinator:) 메서드 역시 MainMapViewController와 관련된 의존성을 관리합니다.
  4. Flow Coordinators
    1. makeStartFlowCoordinator(navigationController:)와 makeMainMapFlowCoordinator(navigationController:) 메서드는 각각 StartCoordinator와 MainMapCoordinator를 생성합니다.
    2. 이 코디네이터들은 앱 내에서 특정 흐름(예: 시작 화면, 메인 지도 화면 등)을 관리하며, DIContainer를 통해 필요한 의존성을 주입받아 각 화면(ViewController)으로 전환을 관리합니다.

결론

이 코드에서는 DIContainer를 사용하여 의존성 주입을 효과적으로 관리합니다. AppDIContainer는 상위 컨테이너로서, 특정 모듈에 대한 DIContainer(SafeAreaDIContainer)를 생성합니다. SafeAreaDIContainer는 각 레이어(예: Use Case, Repository, ViewController)와 각 기능(예: Start, MainMap)에 필요한 객체들을 생성하고, 이를 주입하여 모듈 간 결합도를 낮추고 코드의 유연성과 재사용성을 높입니다.

728x90
반응형