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) -> ()) {
    // 세부 구현
  }
} 

의존성 주입의 이점

  1. 코드 재사용이 용이하다. 공통적인 로직은 한번만 구현하고 필요한 정보 혹은 기능만 끼워넣어서 커스터마이징이 가능하기 때문에 여러 곳에서 재사용이 가능하다.
  2. 테스트가 용이하다. 실제 네트워크 통신과 같은 경우에는 네트워크 상황에 따라 다양한 이유로 실패할 수 있기 때문에 객체의 로직상 오류가 없어도 테스트가 실패할 수 있다. 하지만 이렇게 외부에서 기능을 주입할 수 있다면 실제 네트워크 통신을 하지 않고도 마치 한 것처럼 동작하도록 기능을 만들어서 주입하면, 실제 네트워크 통신을 하지 않아도 테스트가 가능해진다. 이렇게 테스트 용으로 만든 것을 목(mock)이라고 한다.
  3. 객채의 책임을 명확히 알 수 있다. 예를 들어 ImageLoaderNetworkManagerType을 주입시켜야 한다는 사실을 알면 우리는 ImageLoader가 어떤 객체에 의존성이 있고 역할을 하고 있는지 더욱 분명히 인지할 수 있다.

References

https://jcsoohwancho.github.io/2020-12-10-쉽게-이해하는-의존성-주입/

https://sihyungyou.github.io/iOS-dependency-injection/