SwiftInject
종류: Develop
https://github.com/Swinject/Swinject
DI란??(Dependency Injection)
Dependency Injection은 클래스 내부에서 필요한 객체의 인스턴스를, 클래스 내부에서 생성하는 것이 아니라 외부에서 생성한 뒤 이니셜라이저 또는 setter를 통해 내부로 주입받는 것. 이 때 이니셜라이저의 타입은 프로토콜을 활용해서 내부에서는 프로토콜 메서드를 사용
- 의존성(Dependency)
- B class가 바뀌면 A class도 같이 바뀌게 된다면 A는 B에게 의존성이 있다라고 함
class AdChamp { let name: String /// Int가 바뀌게 되면 Player()안에서도 문제남 init(name: String) { self.name = name } } // 플레이어 클래스 class Player { let apMost: ApChamp let adMost: AdChamp // 클래스 내부에서 ApChamp, AdChamp 인스턴스를 생성하고 있다. init() { self.apMost = ApChamp(name: "아칼리") self.adMost = AdChamp(name: "요네") } } let a = Player()
- 주입(Injection)
- 인스턴스를 외부에서 생성해서 넣는 것
class Player { let apMost: ApChamp let adMost: AdChamp // init 함수 수정 init(apMost: ApChamp, adMost: AdChamp) { self.apMost = apMost // 이렇게 외부에 만들어서 넣으면 됨! self.adMost = adMost } }
- 의존성 분리
- 의존 역전의 원칙을 기반으로 분리해야 의존성 분리라고 한다
🤓 DIP 원칙이란
- 의존 관계를 맺을 땐, 변화하기 쉬운 것보단 변화하기 어려운 것에 의존
- 변화하기 어려운 것이란 추상 클래스나 인터페이스를 의미
- 변화하기 쉬운 것은 구체화된 클래스를
→ 즉, 구체적인 클래스가 아닌 인터페이스 또는 추상 클래스와 관계를 맺는다는 것을 의미
// ApChamp 와 AdChamp 이 공통으로 준수할 프로토콜.
// 프로토콜을 사용함으로써 의존 역전이 되었다.
protocol Champ: AnyObject {
var name: String { get }
}
// Champ 프로토콜 채택
class ApChamp: Champ {
let name: String
init(name: String) {
self.name = name
}
}
class Player {
let apMost: Champ // 구체적인 클래스가 아니라 프로토콜을 가지고 있음!!
let adMost: Champ
// 프로토콜에 대고 주입받는다.
init(apMost: Champ, adMost: Champ) {
self.apMost = apMost
self.adMost = adMost
}
}
Q. 여기서 name이 int로 바뀌게 된다면?? 모든 클래스가 에러를 가지게 된다.
즉, 제어 주체가 프로토콜에 있다라는 것!!!!!!!!!!!!
그럼 이제 IOC, 의존 역전을 구현하는 프레임워크인 IOC Container, SwiftInject을 알아보자!
Swinject : Swift 에서 DI (의존성 주입)을 위한 프레임워크. 객체 자체가 아니라 프레임워크에 의해 객체의 의존성이 주입되도록 한다.
//
// Copyright © 2019 Swinject Contributors. All rights reserved.
//
import Swinject
/*:
## Basic Use
*/
// 프로토콜 1
protocol Animal {
var name: String? { get set }
func sound() -> String
}
class Cat: Animal {
var name: String?
init(name: String?) {
self.name = name
}
func sound() -> String {
return "Meow!"
}
}
// 프로토콜 2
protocol Person {
func play() -> String
}
class PetOwner: Person {
let pet: Animal
init(pet: Animal) {
self.pet = pet
}
func play() -> String {
let name = pet.name ?? "someone"
return "I'm playing with \(name). \(pet.sound())"
}
}
// 1. Create a container and register service and component pairs.
// Container: 클래스 외부에서 의존성 주입을 하기 위해 Appdelegate에서 Container를 선언
// - register: Conainer에 사용할 프로토콜을 등록
let container = Container()
container.register(Animal.self) { _ in Cat(name: "Mimi") }
container.register(Person.self) { r in PetOwner(pet: r.resolve(Animal.self)!) }
// - resolve: 클래스를 사용
// The person is resolved to a PetOwner with a Cat.
let person = container.resolve(Person.self)!
print(person.play())
그럼 프로젝트에서 해보자!
- container를 생성
import Foundation
import Swinject
// container 생성
let container: Container = {
let container = Container()
container.register(Repositoriable.self, name: "CryptoRepository") { _ in
CryptoRepository()
}
container.register(Repositoriable.self, name: "NFTRepository") { _ in
NFTRepository()
}
container.register(SwinjectViewModel.self) { resolver in
SwinjectViewModel(
cryptoRepository: resolver.resolve(Repositoriable.self, name: "CryptoRepository")!,
nftRepository: resolver.resolve(Repositoriable.self, name: "NFTRepository")!
)
}
return container
}()
- viewModel에 inject해주기
let viewModel = container.resolve(SwinjectViewModel.self)!
protocol Repositoriable {
func fetch() -> String
}
struct CryptoRepository: Repositoriable {
func fetch() -> String {
"Crypto"
}
}
struct NFTRepository: Repositoriable {
func fetch() -> String {
"NFT"
}
}
class SwinjectViewModel {
private let cryptoRepository: Repositoriable
private let nftRepository: Repositoriable
init(cryptoRepository: Repositoriable, nftRepository: Repositoriable) {
self.cryptoRepository = cryptoRepository
self.nftRepository = nftRepository
}
func onAppear() {
dump(cryptoRepository)
dump(nftRepository)
}
}