clamp
Clamp
clamp
글쓰기 관리
전체 방문자
오늘
어제
  • 분류 전체보기 (509)
    • IOS (85)
    • SwiftUI+TCA+Combine (9)
    • RxSwift + MVVM (56)
    • Clean Architecture (12)
    • SWIFT (56)
    • iOS - TDD (2)
    • 디자인패턴 (4)
    • CS (56)
      • 알고리즘 (29)
      • 운영체제 (15)
      • 자료구조 (2)
      • 네트워킹 (4)
      • 기타 (6)
    • 회고 (0)
    • Firebase (18)
    • SwiftUI (10)
    • iOS - UIKit (11)
    • iOS - 오픈소스 (6)
    • 코딩테스트 (166)
      • 프로그래머스 (164)
    • 정보처리기사 (14)
    • GitHub (2)
글쓰기 / 관리자

블로그 메뉴

  • 홈
  • 태그
  • 방명록

공지사항

인기 글

태그

  • uikit
  • ㅅ
  • Q
  • Swift

최근 댓글

최근 글

티스토리

hELLO · Designed By 정상우.
clamp

Clamp

DI Container
Clean Architecture

DI Container

2023. 5. 11. 13:19

Dependency와 injection & SOLID의 의존관계 역전 원칙(DIP)의 사전지식이 필요하다.

https://clamp-coding.tistory.com/447

 

Dependency Container란 무엇인가.

의존성 주입을 해줄 때는 외부에서 인스턴스를 만들어서 주입해준다. 외부에서 인스턴스를 만들어서 주입해주는 곳은 여러곳일 수 있다.

즉 인스턴스를 만드는 위치가 분산되어있다. 근데 Container라는 것이 있고 Container가 모든 인스턴스를 다 가지고 있고, 관리한다고 생각해보자..

 

Container에 앞으로 내가 사용할 모든 인스턴스를 다 만들어서 등록해두고 => register

필요한 시점에 Container에게 특정 타입의 인스턴스를 달라고하면 Container가 꺼내주는 => resolve

 

것이다!

 

아래같은 코드의 모양이라고 할 수 있다.

import XCTest
import Swinject

@testable import Bitcoin_Adventurer

class BasicTests: XCTestCase {
  
  private let container = Container()
  
  // MARK: - Boilerplate methods
  
  override func setUp() {
    super.setUp()
    // 1
    container.register(Currency.self) { _ in .USD }
    container.register(CryptoCurrency.self) { _ in .BTC }
    
    // 2
    container.register(Price.self) { resolver in
      let crypto = resolver.resolve(CryptoCurrency.self)!
      let currency = resolver.resolve(Currency.self)!
      return Price(base: crypto, amount: "999456", currency: currency)
    }
    
    // 3
    container.register(PriceResponse.self) { resolver in
      let price = resolver.resolve(Price.self)!
      return PriceResponse(data: price, warnings: nil)
    }
  }
  
  override func tearDown() {
    super.tearDown()
    container.removeAll()
  }
  
  // MARK: - Tests
  func testPriceResponseData() {
    let response = container.resolve(PriceResponse.self)!
    XCTAssertEqual(response.data.amount, "999456")
  }
}

 

 

언제나 느끼는 거지만 처음 보는 코드는 막막합니다..

 

쨋든! 필요한 이유를 알아보자면

 

Dependency Container가 필요한 이유

DIContainer를 사용할 경우를 이미지로 그려보면 아래와 같을 수 있다.

 

 

주입해줘야하는게 많아서 이니셜라이저의 파라미터가 엄청 많은데 아래와같은 중복 코드가 있을 때 유용하다.

 

이처럼 주입해줘야 하는 코드가 많고, 중복코드가 많을 때 유용하다.

 

class ClientA {
    func someMethod() {
        let dataFetcher = Container.shared.resolve(DataFetcher.self)
    }
}

class ClientB {
    func someMethod() {
        let dataFetcher = Container.shared.resolve(DataFetcher.self)
    }
}

 

resolve(DataFetcher.self) 할 때 마다 새로운 DataFetcher 인스턴스를 리턴하고있다. 

리턴하는 인스턴스 객체들의 주소는 다르다.

만약 리턴하는 DataFetcher를 하나의 인스턴스(객체)로 하고싶다면 Object Scope로 설정할 수 있다. 추후에 다루기로 한다.

 

 

 

Dependency Container의 내부 구현은 ??

정말 간단한 DIContainer를 만들어본다면 이렇게 될 것이다.

class DIContainer {
    
    static let shared = DIContainer()
    
    private var dependencies = [String: Any]()
    
    private init() {}

    func register<T>(_ dependency: T) {
        let key = String(describing: type(of: T.self))
        dependencies[key] = dependency
    }
    
    func resolve<T>() -> T {
        let key = String(describing: type(of: T.self))
        let dependency = dependencies[key]
        
        precondition(dependency != nil, "\(key)는 register되지 않았어어요. resolve 부르기전에 register 해주세요")
        
        return dependency as! T
    }
}

 

만약 모델이 이렇게 구성되어 있다면?

 

protocol Eatable {
    var calorie: Int { get }
}

protocol CityPresentable {
    var code: String { get }
    var name: String { get }
}

struct Pizza: Eatable {
    var calorie: Int {
        return 300
    }
}

struct Seoul: CityPresentable {
    var code: String {
        return "02"
    }
    
    var name: String {
        return "서울"
    }
}

struct FoodTruck {
    let food: Eatable
    let city: CityPresentable
    
    init(food: Eatable, city: CityPresentable) {
        self.food = food
        self.city = city
    }
}

 

아래와같이 register, resolve 할 수 있을 것이다.

 

class AppDelegate: UIResponder, UIApplicationDelegate {

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        
        registerDependencies()
        
        return true
    }
    
    private func registerDependencies() {
        DIContainer.shared.register(Pizza())
        DIContainer.shared.register(Seoul())
        
        let pizza: Pizza = DIContainer.shared.resolve()
        let seoul: Seoul = DIContainer.shared.resolve()

        DIContainer.shared.register(FoodTruck(food: pizza,
                                              city: seoul))
    }
}

class ViewController: UIViewController {

    let foodTruck: FoodTruck = DIContainer.shared.resolve()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        print(foodTruck)
    }
}

 

 

 

다음에는 Swinject를 활용한 DIContainer를 공부해보려한다.

 

DIContainer의 모든 기능을 직접 구현하기엔 어렵고? Swift에서는 주로 Swinject를 사용한다고 알고있다. 

Swinject는 DIContainer를 구현하고 사용할 수 있도록 도와주는 프레임워크라고 한다. Swinject는 의존성 주입을 위한 컨테이너를 제공하며, 클래스나 객체의 인스턴스를 생성하고 의존성을 주입하는 기능을 제공한다. 

 

쉽게말하면 DIContainer는 기법, Swinject는 DIContainer를 활용하기 위한 라이브러리 정도로 이해가 될 것 같다.

저작자표시 비영리 동일조건 (새창열림)
    'Clean Architecture' 카테고리의 다른 글
    • [Clean Architecture] Repository Pattern in IOS
    • Swinject
    • [iOS] Coordinator Pattern
    • [Clean Architecture] Clean Architecture에서 Entity, Use case, Presenter등 구성요소가 무엇인가.
    clamp
    clamp
    주니어 iOS개발자의 발악!!!!!!!

    티스토리툴바