다양한 의존성 주입 방법을 고려하고, 최종적으로 ViewController에 생성자 주입 방식을 통해 컴파일 타임에 주입 여부를 판단할 수 있도록 변경하였습니다.

- 초기에 작성한 의존성 주입이라고 착각한 코드입니다.
- 내부에서 생성을 하고 있기 때문에, 인터페이스를 분리한 이유가 전혀 없는 코드입니다.

- 메소드로 해당 내부 프로퍼티에 주입하는 방식으로 변경했습니다.
- 하지만 여전히 해당 메서드가 호출되는지 컴파일 타임에 확인할 수 없다는 문제가 발생했습니다.

- 이에 생성자를 통해 주입하는 방식을 시도했습니다.
- 하지만, 사용하는 다른 개발자가, 단순 생성을 했을 시, 뷰모델 주입이 필수적인지 알 수 없습니다.
- 이를 fatal error로 막았으나, 여전히 컴파일 타임에 알 수 없다는 점은 동일합니다.

- 최종적으로, Coder를 통해 생성하는 경우 자체를 컴파일 타임에 막기 위해
available태그를 추가했습니다.

- 결과적으로, DIViewController라는 요소를 만들고, ViewModel이 필요한 경우 해당 VC를 상속하여 구현했습니다.
- 제네릭을 사용하여 어떤 타입이든 가능하도록 하였습니다.
- 스토리 보드를 사용한 경우, iOS13 이후 부터는 아래와 같은 방식으로 사용이 가능합니다.
import UIKit
class DefaultDIViewController<T>: DefaultViewController {
var viewModel: T
init(viewModel: T) {
self.viewModel = viewModel
super.init(nibName: nil, bundle: nil) // code로 VC를 생성하는 경우 nib, bundle 모두 불필요
}
init?(coder: NSCoder, viewModel: T) {
self.viewModel = viewModel
super.init(coder: coder)
}
@available(*, unavailable, renamed: "init(coder:viewModel:)")
required init?(coder: NSCoder) {
fatalError("Invalid way of decoding this ViewController")
}
}let storyboard = UIStoryboard(name: "Main", bundle: nil)
let viewModel = storyboard.instantiateInitialViewController { coder -> RoomListViewController in
let viewModel = DefaultRoomListViewModel(usecase: RoomListUseCase(repository: RoomListRepository(service: FirebaseService.shared)))
return .init(coder: coder, viewModel: viewModel) ?? RoomListViewController(viewModel: viewModel)
}- 결과적으로, ViewController를 생성 시점에 의존성 주입을 하는 방식을 도입하여, 컴파일 타임에 주입 여부를 확인할 수 있도록 변경하였습니다.
의존성 주입의 방법과 의사결정 과정
- 의존성 주입의 방식에는 크게 4가지가 있다.
- 프로퍼티 주입 & 메서드 주입
- 메서드를 사용해서, 혹은 프로퍼티를 적용해서 의존성을 주입하는 방법이다.
- 생성자 주입
- 생성자만 보고 의존성을 파악할 수 있게 된다.
- A와 B가 서로 알아야 하는 경우 문제가 발생한다.
- 이럴 경우 생성자 주입 방식으로 서로의 의존성을 주입하는 것이 불가능하여, 메서드 혹은 프로퍼티 주입 방식을 쓰는 것이 보다 깔끔한 방법일 수 있다.
- 가장 깔끔한 방식이나 까다로울 수 있다.
- 서비스 로케이터 패턴 (안티 패턴)
- 모든 의존성을 알고 있는 locator 객체가 있고, 이 해당 객체에 요청하여 의존성을 해결하는 방법이다.
- 생성자만 보고, 의존성을 파악할 수 없다는 단점이 있다.
- 또한 locator 자체에 의존하게 된다.
- 해당 클래스에서 요청을 하는 형태이기 때문에 외부에서 mock 객체를 갈아끼워 넣어서 테스트하는 것이 불가능해 졌다.
- 따라서 안티 패턴이다.
- 모든 의존성을 알고 있는 locator 객체가 있고, 이 해당 객체에 요청하여 의존성을 해결하는 방법이다.
이중, 우리 프로젝트의 경우 서로 의존성을 주입해야 하는 상황이 발견되지 않기 때문에, 생성자 주입 방식을 도입하는 것이 가장 합리적인 판단이라고 생각한다.