이전 글에서 DI에 대해 알아보았다.
마치 자동차에 엔진의 기능을 사용하기 위해서는, 자동차가 엔진에 의존하고 있는듯이,
외부에서 생성자나 메서드를 통해 의존성을 주입하는 방법에 대해 다뤘다.
오늘은 swift의 DI를 도와주는 외부 라이브러리, Swinject에 대해 알아보자.
먼저 공식 문서로 얕보기 공부를 해보자~🌞
https://github.com/Swinject/Swinject/blob/master/Documentation/DIContainer.md
의존성 주입은 의존성을 해결하기 위해 제어의 역전(Inversion of Control, IoC)을 사용하는 소프트웨어 설계 패턴이다.
DI 컨테이너는 시스템의 타입 의존성을 관리한다.
먼저, 해결해야 하는 타입과 그 의존성을 등록한다.
그 다음 DI 컨테이너를 사용하여, 의존성을 자동으로 해결하는 인스턴스를 가져온다.
근데! 의존성 주입의 구현은 가끔 용어가 다양하니깐, 사전에 명확히 정의하고 가보자
"service instance", "component instance"는 같은 의미이며, 바꿔가며 사용할 수 있다~
service와 그에 해당하는 component는 컨테이너의 'register'메서드로 등록된다.
이 메서드는 service타입과 component의 factory메서드를 받는다.
component가 다른 service에 의존하는 경우, factory 메서드는 전달된 resolver 객체에서 'resolve'를 호출하여, 의존성을 주입할 수 있다.
의존성의 실제 타입은 나중에 component의 인스턴스가 생성될 때 결정된다.
프로토콜, 클래스:
protocol Animal {
var name: String { get }
}
protocol Person {
var name: String { get }
}
class Cat: Animal {
let name: String
init(name: String) {
self.name = name
}
}
class PetOwner: Person {
let name: String
let pet: Animal
init(name: String, pet: Animal) {
self.name = name
self.pet = pet
}
}
1) container 생성
2) register component
3) resolve 메서드 호출
=> 컨테이너에서 서비스 인스턴스 가져오기
let container = Container()
container.register(Animal.self) { _ in Cat(name: "mini") }
container.register(Person.self) { r in
PetOwner(name: "Stepehn", pet: r.resolve(Animal.self)!)
}
이러면 지정된 서비스(프로토콜) 타입의 컴포넌트를 반환한다.
let animal = container.resolve(Animal.self)!
let person = container.resolve(Person.self)!
let pet = (person as! PetOwner).pet
print(animal.name) // "mini" 출력
resolve 메서드가 반환하는 인스턴스는 nil일 수 있다.
만약에 너가 두개 이상의 컴포넌트를 등록하고 싶으면, register에도 이름을 붙여 구분할 수 있다.
class Dog: Animal {
let name: String
init(name: String) {
self.name = name
}
}
🐶
let container = Container()
container.register(Animal.self, name: "cat") { _ in Cat(name: "Mini") }
container.register(Animal.self, name: "dog") { _ in Cat(name: "Hachi") }
그런 다음 등록된 이름으로 service 인스턴스를 가져올 수 있다.
let cat = container.resolve(Animal.self, name: "cat")!
let dog = container.resolve(Animal.self, name: "dog")!
print(cat.name) // "Mini"
print(dog is Dog) / true
register 메서드에 전달되는 factory 클로저는 서비스가 해결될 때 전달되는 인수를 받을 수 있다.
서비스를 등록할 때 인수는 'Resolver' 매개변수 뒤에 지정할 수 있다.
'Resolver' 안쓰면 '_'
container.register(Animal.self) { _, name in
Horse(name: name)
}
container.register(Animal.self) { _, name, running in
Horse(name: name, running: running)
}
그 다음 resolve 메서드를 호출할 때 런타임 인수를 전달한다.
하나의 인수만 전달하는 경우, resolve(_:argument:) 사용
let animal1 = container.resolve(Animal.self, argument: "Spirit")!
print(animal1.name) // "Spirit"
print((animal1 as! Horse).running) // false
두 개 이상의 인수를 전달하는 경우, resolve(-:arguments:, -:)를 사용
let animal2 = container.resolve(Animal.self, arguments: "Lucky", true)!
print(
지정된 서비스에 대한 컴포넌트의 등록은 컨테이너에 내부적으로 생성된 키로 저장된다.
컨테이너는 서비스 의존성을 해결하려고 할 때 이 키를 사용한다.
키는 다음으로 구성된다.
키의 모든 부분이 일치하는 기존 등록이 있는 경우,
기존 등록은 새로운 등록으로 덮어쓰기 된다!!
// 1) 서비스 타입이 다르기 때문에 컨테이너에 공존할 수 있음
container.register(Animal.self) { _ in Cat(name: "Mini") }
container.register(Person.self) { r in
PetOwner(name: "Stephen", pet: r.resolve(Animal.self)!)
}
// 2) 등록 이름이 다르기 때문에 컨테이너에 공존할 수 있음
container.register(Animal.self, name: "cat") { _ in Cat(name: "Mini") }
container.register(Animal.self, name: "dog") { _ in Dog(name: "Hachi") }
// 3) 인수의 수가 다르기 때문에 컨테이너에 공존할 수 있음
container.register(Animal.self) { _ in Cat(name: "Mini") }
container.register(Animal.self) { _, name in Cat(name: name) }
// 4) 인수의 타입이 다르기 때문에 컨테이너에 공존할 수 있음
// string, bool
container.register(Animal.self) { _, name, running in
Horse(name: name, running: running)
}
// bool, string
container.register(Animal.self) { _, running, name in
Horse(name: name, running: running)
}