[TIL / 25.05.18] 의존성 주입 담당 DIContainer를 처음 적용해보았습니다

2025. 5. 18. 02:09·iOS/Swift

 

DIContainer란?

앱의 각 객체들이 어떻게 구성되고 주입될지 정의해주는 중앙 조립소.
UseCase, Repository, ViewModel, ViewController와 같은 컴포넌트들을 하나의 컨테이너에서 만들어주기 때문에, 
객체 간의 생성 책임을 분리하고 코드 가독성을 높이는 데 도움이 된다.
 
- 객체 간 생성 책임 분리란?
누가 누구를 만들지 결정하는 책임을 명확히 나누는 것임. 

class MainViewController: UIViewController {
    private let viewModel = MainViewModel(
        searchUseCase: SearchBooksUseCase(
            repository: BookRepository(networkService: NetworkService())
        )
    )
}

 MainVC가 자기 ViewModel을 직접 만들고 그 ViewModel은 UseCase를 직접 만들고 UseCase는 Repository를 직접 만들고....
모든 객체가 자기가 쓸 객체를 직접 생성하고 있음.
이렇게 하면 나중에 테스트할 때 혹은 확장할 때 난이도가 확 올라가게 됨. (아마 예를 들자면, 이 유즈케이스 같은걸 쓰는 부분을 직접 다시 찾아다녀야하고 직접 다 바꿔줘야하니..)
 
 
일단 이쯤에서 다시 다른 장점들을 말해보자면, 코드 가독성이 향상되고 재사용성과 확장성에도 긍정적이고 테스트 및 모킹에 유리하다.
 
그럼 위에서 직접 체이닝...?은 아닌것같지만 연쇄되어 각자 필요한 객체를 해당 객체 안에서 생성해주는 반면, DIContainer는 방식이 좀 다르다.

// Before: 직접 구성
let repo = BookRepository(network: ...)
let useCase = SearchBookUseCase(repo: repo)
let vm = MainViewModel(useCase: useCase)
let vc = MainViewController(viewModel: vm)

// After: DIContainer
let vc = container.makeMainViewController()

 
이건 그냥 예제인데, 만약 DIContainer에서 미리 VC 생성 메서드를 만들어둔다면 위처럼 일일이 만들어 쓸 필요도 없고 하나의 함수만 호출하면 된다.
 
지금 하고 있는 개인과제 프로젝트 내에서 리팩토링을 거치고 있는데 그 안 DIContainer를 처음 적용해보았다.
코드를 직접 다 가져다 붙히기보다 fork 앱에 뜨는 local changes를 보면서 어떤 변화가 생기는지 한번 보면 좋을 것 같다.
 

import Foundation

final class DIContainer {
    private let networkService: NetworkServiceProtocol
    private let coreDataStorage: CoreDataStorageProtocol
    private let bookRepository: BookRepositoryProtocol
    private let cartBookRepository: CartBookRepositoryProtocol
    private let recentBookRepository: RecentBookRepositoryProtocol
    private let cartBookUseCase: CartBookUseCaseProtocol
    private let recentBooksUseCase: RecentBooksUseCaseProtocol
    private let searchBooksUseCase: SearchBooksUseCaseProtocol

    init() {
        self.networkService = NetworkService()
        self.coreDataStorage = CoreDataStorage()

        self.bookRepository = BookRepository(networkService: networkService)
        self.cartBookRepository = CartBookRepository(coreDataStorage: coreDataStorage)
        self.recentBookRepository = RecentBookRepository(coreDataStorage: coreDataStorage)

        self.cartBookUseCase = CartBookUseCase(cartBookRepository: cartBookRepository)
        self.recentBooksUseCase = RecentBooksUseCase(recentBookRepository: recentBookRepository)
        self.searchBooksUseCase = SearchBooksUseCase(bookRepository: bookRepository)
    }

    func makeMainViewController() -> MainViewController {
        let viewModel = MainViewModel(
            searchBooksUseCase: searchBooksUseCase,
            recentBooksUseCase: recentBooksUseCase
        )

        return MainViewController(viewModel: viewModel, diContainer: self)
    }

    func makeCartBookListViewController() -> CartBookListViewController {
        let viewModel = CartBookListViewModel(cartBookUseCase: cartBookUseCase)

        return CartBookListViewController(viewModel: viewModel)
    }

    func makeBookDetailViewController(book: Book) -> (BookDetailViewController, BookDetailViewModel) {
        let viewModel = BookDetailViewModel(
            book: book,
            cartBookUseCase: cartBookUseCase,
            recentBookUseCase: recentBooksUseCase
        )

        let viewController = BookDetailViewController(bookDetailViewModel: viewModel)

        return (viewController, viewModel)
    }
}

 
일단 이전엔 DIContainer가 없었으므로 이렇게 코드를 가져왔다. 이렇게 VC를 일괄적으로 한 곳에서 만들 수 있는 메서드들을 제공하게 되면,
 

 
각 VC를 만들기 위해 그 안에 요소들을 만들어냈던 과정이 저 빨간 영역 내에 있는 코드에 있고, 이를 DIContainer로 일괄 관리를 하게 되면 저렇게 함수 하나 호출하면 길게 쓸 필요 없이 관리가 가능하다.

+
DIContainer를 싱글톤으로 만들게 될 경우 전역에서 쉽게 접근할 수 있는 점은 좋으나, 후에 테스트를 하게 된다면 mock 데이터를 적용하기 위해 이를 사용하는 모든 코드에 다시 목데이터 기준으로 일일이 바꿔야하는 수고스러움이 있다.
반면, 이 컨테이너를 주입하는 방식으로 하게 될 경우 한번에 관리하기 용이하기 때문에 주입하는 방식으로 구현했다.

싱글톤의 장단점을 얘기하면서 앞서 말한 바와 같이 테스트 측면에선 어떤 차이가 있는지 한번 글로 다뤄봐야겠다.
 


 
의존성 주입, 역전이라는 키워드에 대해서 캠프 초기에 다룬적이 있는데 이 의존성이라는 개념에 대해서 다시 한번 공부해봐야할 필요를 느낀다.
 
졸려서 글이 깔끔하려나 모르겠네요 ..!!

'iOS > Swift' 카테고리의 다른 글

[25.06.24] MOUP 트러블슈팅 - Listener와 Rx의 timeout  (0) 2025.06.24
[TIL / 25.05.27] 날씨 앱 main 페이지 구조 트러블슈팅  (5) 2025.05.27
[TIL / 25.05.13] BookSearchApp Lv 3 트러블슈팅 - Core Data 크래시  (0) 2025.05.14
[TIL / 25.05.12] BookSearchApp 트러블슈팅 - 일부 배경색이 투명한 현상  (2) 2025.05.12
[TIL / 25.05.11] RxSwift.. 처음 공부해볼게요 2  (0) 2025.05.11
'iOS/Swift' 카테고리의 다른 글
  • [25.06.24] MOUP 트러블슈팅 - Listener와 Rx의 timeout
  • [TIL / 25.05.27] 날씨 앱 main 페이지 구조 트러블슈팅
  • [TIL / 25.05.13] BookSearchApp Lv 3 트러블슈팅 - Core Data 크래시
  • [TIL / 25.05.12] BookSearchApp 트러블슈팅 - 일부 배경색이 투명한 현상
subkyu-ios
subkyu-ios
subkyu-ios 님의 블로그 입니다.
  • subkyu-ios
    subkyu-ios 님의 블로그
    subkyu-ios
  • 전체
    오늘
    어제
    • 분류 전체보기 (56)
      • iOS (38)
        • Swift (38)
      • 내일배움캠프 (7)
      • Git, Github (3)
      • Algorithm (6)
      • 회고 (1)
      • 면접 질문 정리 (1)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    트러블슈팅
    사전캠프
    github
    algorithm
    회고
    알고리즘
    stackview
    tabman
    UIKit
    RxSwift
    프로그래머스
    내일배움캠프
    본캠프
    KPT
    til
    의존성 주입
    Swift
    Wil
    TableView
    ios
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
subkyu-ios
[TIL / 25.05.18] 의존성 주입 담당 DIContainer를 처음 적용해보았습니다
상단으로

티스토리툴바