본문 바로가기

Swift 공부

Tuist 모듈화 공부 Swinject 3편 적용기

Swinject를 공부한지 약 1일차 어제는 오전 코로나로인한 몸살 , 그 후에  잠깐 공부 그리고 T1 GenG 경기를 보니하루가 끝나 오늘부터 본격적으로 Swinject을 공부를 시작했다.

 

Swinject의 가장 중요한 것은 OOP 에서 SOILD 중 D 

'의존성 주입'(DI : Dependency Injection)  를 도와주는 라이브러리 이고 

기존의 내 프로젝트들에서

 

class ViewModel{
	var network : NetworkProtocol
    
    init(network : NetworkProtocol){
    	self.network = network
    }


}

이렇게 처리해줬던 부분들을 도와주는 라이브러라고 이해하면 편할것이다.

기존에 DI를 많이 접해보고 약 2주에 걸쳐 OOP 를 공부했을때 DI의 중요성을 깨달았고 실제로 프로젝트에 적용하여 사용해보니

상당히 중요한 요소였구나 라는걸 느껴 DI 개념을 공부하기에는 큰 무리가 없었다.

 

그렇다면 Swinject가 뭐냐?

 

따로 설명하기에는 내용이 길어질 것 같아서...

오늘은 Swinject에 대해서만 알아봅시다.

 

Swinject는 Swift용 경량 종속성 주입 프레임워크입니다.

DI(Dependency Injection)는 종속성을 해결하기 위해 IoC(Inversion of Control)를 구현하는 소프트웨어 디자인 패턴입니다. 패턴에서 Swinject는 앱을 느슨하게 결합된 구성 요소로 분할하여 더 쉽게 개발, 테스트 및 유지 관리할 수 있도록 도와줍니다. Swinject는 Swift 제네릭 유형 시스템과 앱의 종속성을 간단하고 유창하게 정의하는 일급 함수로 구동됩니다

 

여기서 모르는 단어를 하나씩 공부해보면 "Inversion of Control" 라는 단어를 처음 들어보는데 이럴땐 위키피디아..

 

https://ko.wikipedia.org/wiki/%EC%A0%9C%EC%96%B4_%EB%B0%98%EC%A0%84

 

제어 반전 - 위키백과, 우리 모두의 백과사전

 

ko.wikipedia.org

 

위의 예시를 살펴보게되면

 

이런 내용인것같다.

 

class TestClass{
    func testFunctionA(a : String ){
        TestManager.shared.managerFunction(a: a)
    }
    
    func testFunctionB(a : String  , testManager : TestProtocol){
        testManager.managerFunction(a: a)
    }
    
    
}

protocol TestProtocol {
    func managerFunction(a : String)
}

class TestManager : TestProtocol{
    
    static let shared : TestManager = TestManager()
    
    func managerFunction(a : String){
        print("a")
    }
    
}

 

위의 코드를 보면 testFunctionA , testFunctionB는 모두 TestManager 의 managerFunction의 함수를 호출하는 함수인데

 

testFunctionA 같은 경우 모든 기능을 TestClass 에서 관리하고 제어하는 방면

testFunctionB 같은 경우 주입받은 testManager의 함수에 의존하기에 제어가 반전되었다 라고 생각하시면 좋을 것 같다.

 

그래서 Swinject은 이런 IoC를 도와주는 라이브러리 라고 생각하면 좋을것 같다.

 

기본적인 사용방법은 

 

https://github.com/Swinject/Swinject

 

GitHub - Swinject/Swinject: Dependency injection framework for Swift with iOS/macOS/Linux

Dependency injection framework for Swift with iOS/macOS/Linux - GitHub - Swinject/Swinject: Dependency injection framework for Swift with iOS/macOS/Linux

github.com

 

깃허브의 메인의 Doc를 읽어보면 너무나도 쉽게 설명이 되어있기에 따로 설명은 생략하겠습니다.

 

주의사항 

Container Thread로부 터  안전한 Method는  Resolver의 오버로드만 있는 유형이므로 컨테이너에 대한 등록은 스레드로부터 안전하지 않습니다. 

그래서 resolve 등록은 일반적으로 앱이 시작될 때 단일 스레드에서 수행되어야 합니다. <- 보통 Appdelegate ,Scenedelegate 에서 실행

 

Swinject에서 용어 정리

  • Service: A protocol defining an interface for a dependent type.
    종속 유형에 대한 인터페이스를 정의하는 프로토콜
  • Component: An actual type implementing a service.
    서비스를 구현하는 실제 타입ㅂ
  • Factory: A function or closure instantiating a component.
    컴포넌트를 인스턴스화하는 함수 or 클로저
  • Container: A collection of component instances.
    구성 요소 인스턴스의 모음

 

Contanier 에 KeyValue 형식으로 내가 원하는 타입의 아이템들을 등록하고 사용하는 라이브러리

 

키는 다음으로 구성됩니다.

  • 서비스 유형
  • 등록 이름
  • 인수의 수와 유형

그럼 여기서 개인적으로 궁금한사실 KeyValue 형식으로 저장을 진행하게되는데 중복되는 Key는 어떻게 처리가되나?

 

 container.register(TestClass.self) { (r) in
            let test = TestClass()
            test.a = "b"
            return test
        }

        container.register(TestClass.self) { (r) in
            let test = TestClass()
            test.a = "c"
            return test
        }
        
print(Assembler.shared.resolver.resolve(TestClass.self)?.a)
//c


container.register(TestClass.self , name: "name") { (r) in
            let test = TestClass()
            test.a = "b"
            return test
        }

        container.register(TestClass.self , name: "name") { (r) in
            let test = TestClass()
            test.a = "c"
            return test
        }
        
  print(Assembler.shared.resolver.resolve(TestClass.self , name : "name")?.a)
  //c

더 늦게 등록한값을 사용하는거같다 실험끗...

 

이제 가장 궁금한거 서로 의존을 하고있는 아이템들은 어떻게처리하냐? 이 해답은 

공식문서 : https://github.com/Swinject/Swinject/blob/master/Documentation/CircularDependencies.md

 

GitHub - Swinject/Swinject: Dependency injection framework for Swift with iOS/macOS/Linux

Dependency injection framework for Swift with iOS/macOS/Linux - GitHub - Swinject/Swinject: Dependency injection framework for Swift with iOS/macOS/Linux

github.com

 에 잘나와있고 내가 이해한 내용을 바탕으로 다시한번 글을 써볼려고한다.

 

protocol ParentProtocol: AnyObject { }
protocol ChildProtocol: AnyObject { }

class Parent: ParentProtocol {
    var child: ChildProtocol?
}

class Child: ChildProtocol {
    weak var parent: ParentProtocol?
}

 

let container = Container()
container.register(ParentProtocol.self) { r in
    let parent = Parent()
    parent.child = r.resolve(ChildProtocol.self)!
    return parent
}
container.register(ChildProtocol.self) { _ in Child() }
    .initCompleted { r, c in
        let child = c as! Child
        child.parent = r.resolve(ParentProtocol.self)
    }

그냥 register로 등록을 하는것이 아닌 initCompleted를 통해 해결을 한다.

 

 

 

Swinject 심화 공부자료

https://ali-akhtar.medium.com/ios-dependency-injection-using-swinject-9c4ceff99e41

 

iOS Dependency Injection Using Swinject

In this article, you’ll find everything you should know about iOS Dependency Injection with Swinject.

ali-akhtar.medium.com

 

Swinject 심화 공부자료 보충 InstanceStorage에 대한 소개

https://github.com/Swinject/Swinject/blob/master/Sources/InstanceStorage.swift

 

GitHub - Swinject/Swinject: Dependency injection framework for Swift with iOS/macOS/Linux

Dependency injection framework for Swift with iOS/macOS/Linux - GitHub - Swinject/Swinject: Dependency injection framework for Swift with iOS/macOS/Linux

github.com

 

 

이론공부는 여기까지만 하고 실제로 사용을 해볼려고한다.

 

대부분의 등록은 Assembler를 통해 파편화시켜서 등록을 시킬 예정이다.

 

처음으로 AppMainCoordinator를 Swinject를 이용하여 등록 할려고한다.

 

import Swinject
import UIKit

class CoordinatorAssembly : Assembly {
    func assemble(container: Container) {
        container.register(AppCoordinator.self) { (r , window : UIWindow) in
            let appCoordinator = AppCoordinator(window: window)
            return appCoordinator
        }
    }
}

CoordinatorAssembler 만들어 AppCoordinator를 등록하고 window가 필요하기에 UIWindow를 변수로 받는다.

 

extension Assembler {
    static let shared: Assembler = {
        let assembler = Assembler(
            [
                //위에있는 아이템들이 좀 더 로우 레벨임
                CoordinatorAssembly()
                
            ],
            container: .init())
        return assembler
    }()
}

아이템을 등록해주고

 

import UIKit
import Swinject

@main
class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: UIWindow?
    var appMainCoordinator : AppCoordinator?
    
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        // Override point for customization after application launch.
        let window = UIWindow()
//        self.window = window
//        appMainCoordinator = AppCoordinator(window: self.window!)
//        appMainCoordinator?.start()
        
        appMainCoordinator = Assembler.shared.resolver.resolve(AppCoordinator.self, argument: window)
        appMainCoordinator?.start()
        return true
    }
}

기존에 AppCoordinator를 직접 만들었던것을 Assembler에서 꺼내와서 사용해준다.

 

 

이렇게 아주 간단하게 Swinject의 사용 예제를 알아보았고 공부하였다 

이제 프로젝트내 직접 의존성 주입을 하던것들을 좀 더 구조화하여 의존성을 주입해볼려고한다

 

공부해야할 것

View단위로 의존성을 만들어야하는지 , 아니면 Coordinator , ViewModel , View 단위로 Assembler를 만들어 사용할지 좀 더 많은 자료들을 공부해볼 예정이다.

 

 

https://github.com/gnejfejf2/TuistTestApp/tree/20220403Tuist3

 

GitHub - gnejfejf2/TuistTestApp

Contribute to gnejfejf2/TuistTestApp development by creating an account on GitHub.

github.com

 

 

해당 코드의 업데이트는 모두 이곳에서 이뤄질 예정이다.

 

'Swift 공부' 카테고리의 다른 글

Async / await의 도입 스위프트 5.5  (0) 2022.04.05
Swift WMO  (0) 2022.04.04
Tuist 모듈화 공부 Swinject 2편 잡담  (0) 2022.04.01
Static Dispatch vs Dynamic Dispatch  (0) 2022.03.30
Tuist , 모듈화 공부 기록기  (0) 2022.03.26