Swift - Dependency Injection(DI)
의존성 주입에 대해 알아봅시다!
의존성이란?
의존성은 코드에서 두 모듈 간의 연결이 이루어지는 것을 말한다. 객체지향언어에서는 두 클래스 간의 관계라고도 하며, 보통 두 객체 중 하나가 다른 객체를 특별한 용도를 위해 사용한다. 이미지를 불러올 객체가 실제 네트워크 요청을 수행하는 객체를 통해 데이터를 불러오는 경우가 예가 될 수 있다.
class ImageLoader {
var networkManager: NetworkManger = NetworkManager()
}
class NetworkManager {
// 네트워크 요청 수행
}
이 같은 경우 ImageLoader
클래스에서 NetworkManger
클래스에 대한 의존관계가 생성된다.
의존성 주입?
의존성 주입은 위에서 본 의존성이라는 것을 외부에서 주입시켜주는 것이다. 즉, 위에서 본 코드처럼 인스턴스 생성의 책임을 객체가 스스로 갖는 것이 아니라, 외부에서 이니셜라이저, 메소드의 인자 등의 형태로 인스턴스를 넘겨주는(주입) 것이다.
아래 코드와 같이 네트워킹을 위한 프로토콜을 정의했다고 생각해보자.
protocol NetworkManagerType {
func networkReqeust(_ url: URL, _ complete: @escaping (Data) -> ())
}
class ImageLoader {
let networkManager: NetworkManagerType
let url: URL
init(networkManager: NetworkManagerType, url: URL) {
self.networkManager = networkManager
self.url = url
}
func loadImage(_ complete: @escaping (UIImage) -> ()) {
// 세부 구현
}
}
let imageLoader = ImageLoader()
imageLoader.networkManager = RequestA()
imageLoader.networkManager = RequestB() // 필요에 맞게 다른 객체를 주입
이처럼 외부에서 ImageLoader
에 의존성 주입을 해준다면 상황에 따라 NetworkManagerType
프로토콜을 채택하는 어떤 객체 타입이든 networkManager
변수로 대체할 수 있다. 즉, ImageLoader
객체는 다른 객체에 대한 디테일에 대해 전혀 알지 못해도 자신의 역할을 수행할 수 있다.
의존성 주입 방법
프로퍼티를 통한 의존성 주입
class ImageLoader {
var networkManager: NetworkMangerType?
}
// 인스턴스의 프로퍼티를 통해 의존성을 주입
let imageLoaderClass = ImageLoader()
imageLoaderClass.networkManager = MentworkManager()
이니셜라이저를 통한 의존성 주입
class ImageLoader {
let networkManager: NetworkManagerType
let url: URL
init(networkManager: NetworkManagerType, url: URL) {
self.networkManager = networkManager
self.url = url
}
func loadImage(_ complete: @escaping (UIImage) -> ()) {
// 세부 구현
}
}
메소드를 통한 의존성 주입
class ImageLoader {
func loadImage(_ networkManager: NetworkManagerType, _ url: URL, _ complete: @escaping (UIImage) -> ()) {
// 세부 구현
}
}
의존성 주입의 이점
- 코드 재사용이 용이하다. 공통적인 로직은 한번만 구현하고 필요한 정보 혹은 기능만 끼워넣어서 커스터마이징이 가능하기 때문에 여러 곳에서 재사용이 가능하다.
- 테스트가 용이하다. 실제 네트워크 통신과 같은 경우에는 네트워크 상황에 따라 다양한 이유로 실패할 수 있기 때문에 객체의 로직상 오류가 없어도 테스트가 실패할 수 있다. 하지만 이렇게 외부에서 기능을 주입할 수 있다면 실제 네트워크 통신을 하지 않고도 마치 한 것처럼 동작하도록 기능을 만들어서 주입하면, 실제 네트워크 통신을 하지 않아도 테스트가 가능해진다. 이렇게 테스트 용으로 만든 것을 목(mock)이라고 한다.
- 객채의 책임을 명확히 알 수 있다. 예를 들어
ImageLoader
에NetworkManagerType
을 주입시켜야 한다는 사실을 알면 우리는ImageLoader
가 어떤 객체에 의존성이 있고 역할을 하고 있는지 더욱 분명히 인지할 수 있다.