[iOS] Dependency Injection

Hyunndy·2023년 1월 31일
0

🐸

iOS-Clean-Architecture-MVVM 예제를 까보다 보니 DIContainer 개념이 나왔다.

의존성 주입이라는 단어가 괜시리 단어가 너무 어렵게 느껴지는 것 같다. (왜지) 아무튼 그동안 정리를 회피해왔는데, 개념을 찾다가 아주 좋은 글을 발견해서 정리해두려고 한다.


내가 학습한 예제에서는 DIContainer가 Scene에게 공통 네트워크 모듈을 주입? 외부에서 생성해서 SceneContainer 객체에 연결? 해줬다.

let appDIContainer = AppDIContainer()

let sceneDIContainer = SceneDIContainer()
sceneDIContainer.networkService = appDIContainer.apiDataTrasferService
sceneDIContainer.imageDataNetworkService = appDIContainer.imageDataTransferervice()

대략 이런식으로.

이 코드를 보자면 의존성 주입이라는 개념을

👩‍💻 그 기능 A VC랑 B VC랑 같이 써야되는데 A VC에만 맞게 구현되어있어서 뜯어내서 B VC랑도 같이 쓸 수 있게 해야될 것 같아요.

➡ 기능을 다른 VC에서도 쓸 수 있게 추상화해서 모듈화 한 다음 각 VC의 initializer에 넣어주던지 변수로 넘겨주던지 하게 합시다.
➡ (각 VC의 initializer에 넣어주던지 변수로 넘겨주던지 하게 합시다.) == DI

요렇게 이해해 볼 수 있지 않을까?!!!

여기까지 생각하고 다른 블로그를 검색해봤는데,
아주 좋은 글을 발견했다.
iOS) DI(Dependency Injection) in Swift

Dependency Injection in Swift 영상을 보고 작성하신 글인데 아주 공감가는 글귀가 나온다. ㅋㅋㅋ

맞아.. 별거 아니야!
10분짜리 영상이라 후딱 보며 정리해보겠다.


저 비유를 한 James Shore은 DI에 대해 아주 직관적인 정의를 내렸다.

DI means giving an object its instance variables. -James Shore-

DI는 그저 오브젝트에게 인스턴스 변수를 주는거야!!
객체가 디펜던시를 스스로 만드는 책임을 지게하지 않고, 인스턴스 변수로 객체에게 디펜던시를 주입(전달) 해주는거야!!

의존성 주입을 하지 않고, 일반적으로 ViewController에 Manager를 정의해보자.

class ViewController: UIViewController {
    var dataManager = DataManager()
}

viewController 내부에 변수를 선언해준다.

여기서 Manager를 주입시키게 코드를 바꾸면 이렇게 된다.

let vc = ViewController()
vc.requestManager = RequestManager()

하지만 대부분의 개발자가 unNecessary option이라고 생각한다.
그냥 바로 ViewController에다가 선언해버리지! 깔끔허지 못하게..!

조금 더 DI의 강점을 살리게끔 코드를 다시 짜보면

protocol Serializer {
    func serialize(data: Any) -> Data?
}

class ImageRequestSerializer: Serializer {
    func serialize(data: Any) -> Data? {
        return nil
    }
}

class DataRequestSerializer: Serializer {
    func serialize(data: Any) -> Data? {
        return nil
    }
}


class DataManager {
    var serializer: Serializer?
}

// 외부에서 주입!
var dataManager = DataManager()
dataManager.serializer = ImageRequestSerializer()
dataManager.serializer = DataRequestSerializer()

추상화된 Serializer 프로토콜을 채택한 각기 다른일을 하는 RequestManager 객체들이 있고,
VC는 상황에 맞는 request 객체를 받을 수 있습니다.

그럼 이렇게 해서 얻는게 뭘까요?

1️⃣ Transparency

클래스나 구조체의 responsibility, requirements가 더 명확해집니다.
위 예제로 보면 VC가 Serializer을 주입해줘야하는걸 알고있다면 우리는 이 VC가 어떤 객체에 의존성이 있고 어떤 역할을 하고있는지 더 분명히 할 수 있습니다.

2️⃣ Testing

유닛 테스팅에서 mock object를 사용하여 behavior을 분리할 수 있다는점에서 굉장히 편리해집니다.
(제가 유닛테스트 지식이 없어서 이건 나중에 다시 쓰겠습니다~)

3️⃣ Separation of Concernes, Coupling

그럼 방법론으로, DI를 코드상에서 어떻게 하는걸까요

Types of Dependency Injection

세 방법이 있습니다.

1️⃣ Initializer Injection

말그대로 초기화단계에서 주입하는 것 입니다.
명확하게 디펜던시가 뭔지 알 수 있습니다.

class DataManager {
    var serializer: Serializer?
    
    init(serializer: Serializer?) {
        self.serializer = serializer
    }
}

// 외부에서 주입!
var dataManager = DataManager(serializer: ImageRequestSerializer())

2️⃣ Property Injection

프로퍼티로 주입합니다.
하지만 바뀔 위험이 있으니 조심해야합니다!

var dataManager = DataManager()
dataManager.serializer = ImageRequestSerializer()

3️⃣ Method Injection

serializer가 매개변수로 갖고있는게 아닌 경우 사용할 수 있는 방법이다.
use case에 따라 유연하게 대응할 수 있다.

class DataManager {
    
    func serialRequest(_ request: URLRequest, with serializer: Serializer) -> Data? {
        return nil
    }
}

상황에 맞게 골라쓰면 될 것 같습니다.


느낀점🙌

여기까지 DI, 의존성 주입을 정리했습니다.
5센트짜리를 25불로 생각하고있었던게 맞네요 ㅎㅎ
저도 모르게 계속 쓰고있었던 것입니다.

ReactorKit에서 reactor를 세팅해줄 때도 전 DI를 사용하고 있었네요.
그래도 정리하고 나니 뿌듯헙니다.

profile
https://hyunndyblog.tistory.com/163 티스토리에서 이사 중

0개의 댓글