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
위의 예시를 살펴보게되면
이런 내용인것같다.
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
깃허브의 메인의 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
에 잘나와있고 내가 이해한 내용을 바탕으로 다시한번 글을 써볼려고한다.
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
Swinject 심화 공부자료 보충 InstanceStorage에 대한 소개
https://github.com/Swinject/Swinject/blob/master/Sources/InstanceStorage.swift
이론공부는 여기까지만 하고 실제로 사용을 해볼려고한다.
대부분의 등록은 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
해당 코드의 업데이트는 모두 이곳에서 이뤄질 예정이다.
'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 |