Notice
Recent Posts
Recent Comments
Link
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
Archives
Today
Total
관리 메뉴

sanichdaniel의 iOS 개발 블로그

Pure DI 본문

dependency injection

Pure DI

sanich8355 2020. 10. 4. 19:23
의존성 주입을 위해 DI 컨테이너를 사용하는 방식을 채택했다.
이때 어느곳에서 DI 컨테이너로부터 의존성을 받아와야 할까라는 의문이 들었다.
예를들어 A 객체는 외부에 의존성이 있고, A 객체는 B 객체에서 생성되는 상황이다.
A가 직접 컨테이너에 접근해서 의존성을 받아와야 할까?
그러면 A가 컨테이너에 의존하게 된다.
B가 A를 생성할때 컨테이너에 접근한다면 B가 컨테이너에 의존성이 생기니, 의존성 해결을 생성해주는 쪽으로 미룬것이나 다름없다.
이런 고민을 하던중 Pure DI 에대해 알게되었다.  

Pure DI

DI 컨테이너 없이, Composition Root 한곳에서 모든 의존성이 주입된다.

출처: blog.ploeh.dk/2014/06/10/pure-di/ 

Compositon Root

의존성 트리가 그려지는 곳으로 보통 앱의 시작점이 Composition Root가 되는 경우가 많다

iOS에서는 AppDelegate가 Composition Root가 되기 적절하다.

만약 DI Container을 사용한다면, Compostion Root에만 노출이 되어야한다.

아니면 DI 컨테이너에 의존성이 생겨버리기에 Service Locator Anti 패턴으로 이어질 가능성이 크다.

Swift로 구현해보기

AppDependency 를 구현

resolve() 함수에서 모든 의존성을 주입한다

아래 예시에서 rootViewController는 MainViewController이다. MainViewController는 DetailViewController을 띄운다. 

struct AppDependency {
    let window: UIWindow
}

extension AppDependency {
    static func resolve() -> AppDependency {
        let window = UIWindow(frame: UIScreen.main.bounds)
        window.backgroundColor = .white
        window.makeKeyAndVisible()
        
        let network = Network()
        
        let detailViewControllerFactory = { item in
            DetailViewController(
                network: network,
                item: item
            )
        }
        let mainViewController = MainViewController(detailFactory: detailFactory)
        window.rootViewController = mainViewController
        
        return AppDependency(
            network: network,
            navigator: navigator,
            window: window
        )
    }
}

DetailViewController는 런타임에 lazy하게 생성된다.

DetailVC의 의존성중 network는 compostion root에서 주입되지만, item은 MainVC에서 DetailVC를 띄우는 순간에 주입되어야한다.

class MainViewController: UIViewController {
    let detailViewControllerFactory: (Int) -> DetailViewController
    
    init(detailViewControllerFactory: @escaping (Int) -> DetailViewController) {
        self.detailViewControllerFactory = detailViewControllerFactory
        super.init(nibName: nil, bundle: nil)
    }
    
    func presentDetail() {
        let detailVC = self.detailViewControllerFactory(3)
        self.present(detailVC, animated: true, completion: nil)
    }
}

class DetailViewController: UIViewController {
    let network: NetworkType
    let item: Int
    
    init(network: NetworkType, item: Int) {
        self.network = network
        self.item = item
        super.init(nibName: nil, bundle: nil)
    }
}

 

lazy하게 주입되는 의존성을 위해, 위의 예시에서 detailViewControllerFactory 클로져를 만들었다.

이 클로져는 composition root에서 주입된다

lazy하게 주입되는 item은 MainVC에서 띄우는 순간 클로져에 인자로 넘겨지고 DetailVC가 생성된다.  

 

AppDelegate에서 AppDependency의 resolve를 호출하여 모든 의존성 주입

class AppDelegate {
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        let dependency = AppDependency.resolve()
        self.window = dependency.window
    }
}

장점

1. 앱의 의존성 트리를 알 수 있다

2.직관적이다. (DI 컨테이너를 사용하는 경우 객체 생성이 컨테이너에 숨겨져있다)

3.DI 컨테이너에 대해 따로 공부할 필요가 없다. (DI컨테이너를 안쓴다는 전제하에)

단점

1. 한 클래스의 의존성 (생성자)가 바뀔때마다 Compostion Root에서 코드를 수정해줘야하기에 유지관리가 까다롭다

2. 큰 코드베이스에서는 리펙토링을 하기 까다롭다

 

출처

freecontent.manning.com/dependency-injection-in-net-2nd-edition-understanding-the-composition-root/

freecontent.manning.com/dependency-injection-in-net-2nd-edition-understanding-the-composition-root/?utm_source=freecontentcenter&utm_medium=website&utm_campaign=book_dependencyinjection_ppp&utm_content=article_04

'dependency injection' 카테고리의 다른 글

Dagger를 참고한 프로퍼티 래퍼를 이용한 DI  (0) 2020.10.04
Dagger2 알아보기  (0) 2020.10.04
Service Locator vs DI  (0) 2020.10.03
DI 컨테이너 장단점  (0) 2020.10.02