๐ง๐ปโ๐ป ์๋ง ์ข
์ฐฌ ๋ฉํ ๋์ ๋ฉํ ๋ง ํ๋ก์ฐ์
์ ์ํด ์ ๋ฆฌํ ๊ธ
(2022๋
์ ์์ฑํ ๊ฒ์๊ธ์ด์๋๋ฐ, 2023๋
์ ์กฐ๊ธ ์์ ํ๊ณ ๋ณด์ํ์ต๋๋ค.)
๐ญ 2023.04 ์ถ๊ฐ ๋ด์ฉ : DI์ Swinject๋ฅผ ๋ ๊ณต๋ถํด์ Toy Project์ ์ ์ฉํด๋ดค์ต๋๋ค. ์๋ ์ฝ๋๋ฅผ ์ฐธ๊ณ ํ๋ฉด Swinject์ ๋ํ ์ดํด๋๋ฅผ ๋ ๋์ด๋ ๋ฐ ์ฐธ๊ณ ๊ฐ ๋ ๊ฒ ๊ฐ์ต๋๋ค.
๐ DI, Swinject ๋ฅผ ์ ์ฉํ [์๋ง ๋ ์จ ์ฑ] : https://github.com/heyksw/Soma-WeatherApp
์ด ๋ ๊ตฐ๋ฐ๋ฅผ ์ดํด๋ณด๋ฉด ๋ฉ๋๋ค.
๋ฉด์ ์์ "DI๊ฐ ๋ญ๋"๊ณ ํ๋ฉด ๋๋ ์ด๋ ๊ฒ ๋๋ตํ ๊ฑฐ๋ค.
"Dependency Injection์ ํด๋์ค ๋ด๋ถ์์ ํ์ํ ๊ฐ์ฒด์ ์ธ์คํด์ค๋ฅผ, ํด๋์ค ๋ด๋ถ์์ ์์ฑํ๋ ๊ฒ์ด ์๋๋ผ ์ธ๋ถ์์ ์์ฑํ ๋ค ์ด๋์ ๋ผ์ด์ ๋๋ setter๋ฅผ ํตํด ๋ด๋ถ๋ก ์ฃผ์ ๋ฐ๋ ๊ฒ์ ๋๋ค. ์ด ๋ ์ด๋์ ๋ผ์ด์ ์ ํ์ ์ ํ๋กํ ์ฝ์ ํ์ฉํด์ ๋ด๋ถ์์๋ ํ๋กํ ์ฝ ๋ฉ์๋๋ฅผ ์ฌ์ฉํฉ๋๋ค."
Dependency Injection์ ์ง์ญํ๋ฉด, ์์กด์ฑ ์ฃผ์
์ด๋ค.
DI ๋ "์์กด์ฑ" ์ ํด๋์ค์ "์ฃผ์
" ์ํค๋ ๊ฒ์ด๊ณ , "์์กด์ฑ ๋ถ๋ฆฌ"์ ์กฐ๊ฑด์ ๋ง์กฑํด์ผ ํ๋ค.
๐ง ์ ํํ ์ดํด๋ฅผ ์ํด ๋ค์ 4๊ฐ์ง ๊ฐ๋ ์ ์์๋๋ก ์ ๋ฆฌํ๋ค.
ํด๋์ค A, ํด๋์ค B๊ฐ ์๊ณ , ํด๋์ค B์ ๊ฐ์ด ๋ฐ๋ ๋ ํด๋์ค A์ ๊ฐ๋ ํจ๊ป ๋ฐ๋๊ฒ ๋๋ค๋ฉด ํด๋์คA ๋ B์๊ฒ ์์กด์ฑ์ ๊ฐ๋๋ค๊ณ ๋งํ๋ค.
LOL ์ ์๋ก ๋ค์ด๋ดค๋ค. ํ๋ ์ด์ด๊ฐ ์๊ณ , ํ๋ ์ด์ด๋ AP ์ฑํผ์ธ ์ค ๊ฐ์ฅ ์ํ๋ ์ฑํผ์ธ ํ๋, AD ์ฑํผ์ธ ์ค ๊ฐ์ฅ ์ํ๋ ์ฑํผ์ธ ํ๋์ฉ์ ๊ฐ๊ณ ์๋ค.
// AP ์ฑํผ์ธ ํด๋์ค
class ApChamp {
let name: String
init(name: String) {
self.name = name
}
}
// AD ์ฑํผ์ธ ํด๋์ค
class AdChamp {
let name: String
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 ksw = Player()
print(ksw.apMost.name) // ์์นผ๋ฆฌ
print(ksw.adMost.name) // ์๋ค
๋ง์ฝ ์ฌ๊ธฐ์ AdChamp ํด๋์ค์ name ํ์ ์ ์ด๋ ๊ฒ Int๋ก ๋ณ๊ฒฝํ๋ค๋ฉด,
// AD ์ฑํผ์ธ ํด๋์ค
class AdChamp {
let name: Int
init(name: Int) {
self.name = name
}
}
ksw ์ init() ํจ์ ๋ด๋ถ์์ ํ์ ์๋ฌ๊ฐ ๋๊ฒ๋๋ค. ์ฆ, Player ํด๋์ค์๋ ์ํฅ์ ๋ผ์น๋ค. ์ด๋ฐ ๊ฒฝ์ฐ๋ฅผ Player ํด๋์ค๊ฐ AdChamp ํด๋์ค๋ฅผ ์์กดํ๋ค ๋ผ๊ณ ํํํ๋ค.
์ฌ๊ธฐ์๋ ํนํ Player ํด๋์ค์ init() ๋ด๋ถ์์ ApChamp(), AdChamp() ์ธ์คํด์ค๋ฅผ ์์ฑํ๊ณ ์๋ค๋ ๊ฑธ ์ฃผ๋ชฉํด์ผํ๋ค.
์์ Player ํด๋์ค ์ฝ๋๋ฅผ ๋ค์๊ณผ ๊ฐ์ด ์์ ํ ์ ์๋ค.
class Player {
let apMost: ApChamp
let adMost: AdChamp
// init ํจ์ ์์
init(apMost: ApChamp, adMost: AdChamp) {
self.apMost = apMost
self.adMost = adMost
}
}
// ์ธ๋ถ์์ ํด๋์ค ๋ด๋ถ์ ๊ฐ์ "์ฃผ์
"
let apChamp = ApChamp(name: "์์นผ๋ฆฌ")
let adChamp = AdChamp(name: "์๋ค")
let ksw = Player(apMost: apChamp, adMost: adChamp)
print(ksw.apMost.name) // ์์นผ๋ฆฌ
print(ksw.adMost.name) // ์๋ค
๊ฐ์ฒด์ ์ธ์คํด์ค๋ฅผ ์ธ๋ถ์์ ์์ฑํ ๋ค ๋ฃ์ด์ฃผ๋ ๊ฒ์ ์ฃผ์ ์ด๋ผ๊ณ ํ๋ค.
ํ์ฌ ์ฝ๋์์ AdChamp์ name์ด Int ํ์ผ๋ก ๋ณ๊ฒฝ๋๋ค๊ณ ํด๋, Player ํด๋์ค์ init()์๋ ๋ฌธ์ ๊ฐ ์๊ธฐ์ง ์๊ฒ ๋๋ค. ๊ทธ๋๋ AdChamp์ name value ๊ฐ ๋ฐ๋๋ค๋ฉด, Player์ adMost ์ name value ๋ ๋ฐ๋๋ฏ๋ก ์์กด์ฑ์ ๋จ์์๋ค. ์ฝ๊ฒ ๋งํด์, ํด๋์ค A ๋ด๋ถ์์ ๋ค๋ฅธ ํด๋์ค B๋ฅผ ์ฌ์ฉํ๊ณ ์๋ค๋ฉด, A๊ฐ B๋ฅผ ์์กดํ๋ค๊ณ ์๊ฐํ ์ ์๋ค.
์ฌํผ ์ด๋ ๊ฒ ํด์ "์์กด์ฑ"์ "์ฃผ์ "ํ๋ค.
์์ ์ฝ๋๊น์ง ์์กด์ฑ ์ฃผ์ ์ ์ดํด๋ดค๋ค. ํ์ง๋ง ์ผ๋ฐ์ ์ผ๋ก ์์กด์ฑ์ ์ฃผ์ ์ํจ ๊ฒ ๋ง์ผ๋ก DI ๋ผ๊ณ ๋ถ๋ฅด์ง๋ ์๋๋ค.
์์กด์ฑ ๋ถ๋ฆฌ์ ์กฐ๊ฑด์ ๋ง์กฑ์์ผ์ผ DI ๋ผ๊ณ ํ๋ค.
๊ทธ๋ฆฌ๊ณ ์์กด์ฑ ๋ถ๋ฆฌ๋ "์์กด ์ญ์ ์ ์์น"์ ๊ธฐ๋ฐ์ผ๋ก ๋ถ๋ฆฌ๋ฅผ ์คํํ๋ค.
SOLID 5์์น ์ค Dependency Inversion Principle ์ ์๋ฏธํ๋ค.
๐ค DIP ์์น์ด๋
๋ฐ๋ผ์ Swift์ Protocol ์ ์ฌ์ฉํด์ค๋ค.
// ApChamp ์ AdChamp ์ด ๊ณตํต์ผ๋ก ์ค์ํ ํ๋กํ ์ฝ.
// ํ๋กํ ์ฝ์ ์ฌ์ฉํจ์ผ๋ก์จ ์์กด ์ญ์ ์ด ๋์๋ค.
protocol Champ: AnyObject {
var name: String { get }
}
// Champ ํ๋กํ ์ฝ ์ฑํ
class ApChamp: Champ {
let name: String
init(name: String) {
self.name = name
}
}
// Champ ํ๋กํ ์ฝ ์ฑํ
class AdChamp: 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
}
}
// ์ธ๋ถ์์ ํด๋์ค ๋ด๋ถ์ ๊ฐ์ "์ฃผ์
"
let apChamp = ApChamp(name: "์์นผ๋ฆฌ")
let adChamp = AdChamp(name: "์๋ค")
let ksw = Player(apMost: apChamp, adMost: adChamp)
print(ksw.apMost.name) // ์์นผ๋ฆฌ
print(ksw.adMost.name) // ์๋ค
์ด๋ ๊ฒ ํ๋กํ ์ฝ์ ํ์ฉํ์ ๋ ์ฅ์ ์ ๋ค์๊ณผ ๊ฐ๋ค.
ex) ํ๋กํ ์ฝ์์ champion ์ ํ์
์ String ์์ Int ํ์ผ๋ก ๋ณ๊ฒฝํ๋ค๋ฉด ?
โ ( Type 'ApChamp' does not conform to protocol 'Champ' )
๋ชจ๋ ํด๋์ค๋ค์ด ํจ๊ป ์๋ฌ๋ฅผ ๊ฐ์ง๊ฒ ๋๋ค. ApChamp, AdChamp 2๊ฐ ํด๋์ค์ ์ ์ด ์ฃผ์ฒด๊ฐ ๋ชจ๋ "ํ๋กํ ์ฝ"์๊ฒ ์๊ฒ ๋๋ค. ํ๋กํ ์ฝ ํ๋๋ง ์ ํ์ ํ๋ค๋ฉด, ๊ทธ ํ๋กํ ์ฝ์ ์ค์ํ๋ ๋ชจ๋ ํด๋์ค๋ฅผ ์ ์ดํ๊ณ ๋ถ์ํ๊ธฐ ์ฌ์์ง๊ฒ ๋๋ค.
ํ๋กํ ์ฝ์ ์ฌ์ฉํ์ง ์์์ฑ name์ ํ์ ์ StringโInt ํ์ผ๋ก ์ ์ง๋ณด์ ํ๊ฒ ๋ ์ผ์ด ์๊ธด๋ค๋ฉด ๋ชจ๋ ํด๋์ค๋ฅผ ์ผ์ผํ ํ์ธํด์ค์ผ ํ๋ ๋ฒ๊ฑฐ๋ก์์ด ์๊ธธ ๊ฑฐ๋ค.
์ด๋ฐ ๊ณผ์ ์ด ์ ์ผ์ด๋ฌ์ ๋ ํด๋์ค์์ ํ๋กํ ์ฝ์๊ฒ ์์กด์ ๋ฐฉํฅ์ด ์ญ์ ๋์๋ค๊ณ ํ๋ค.
= IOC (Inversion Of Control), ์ ์ด ๋ฐ์ ์ด๋ผ๊ณ ๋ ํ๋ค.
๊ทธ๋ฆฌ๊ณ DI๋ฅผ ํ์ ๋ ๋ ํฐ ์ฅ์ ์ ์ด๋ฐ ๊ฒฝ์ฐ๋ค.
ex) ๊ฐ์๊ธฐ ๊ธฐํ์๊ฐ Player์ apMost์ ์๋ฆฌ์ ๋ฐ๋์ ์ํฌํฐ ์ฑํผ์ธ์ ๋ฃ์ด๋ฌ๋ผ๊ณ ์๊ตฌํ๋ค. ๊ทผ๋ฐ ๋ฑ๋ณด๋๊น ๋ ๊ทธ ๋ค์ ๋ฒ์ ์๋ ๋ง๋ฒ์ฌ ์ฑํผ์ธ์ ๋ฃ์ผ๋ผ๊ณ ๋ณ๋์ ๋ถ๋ฆด๊ฒ ๋ปํ๋ค.
// Support ์ฑํผ์ธ ํด๋์ค
class SupportChamp: Champ {
let name: String
init(name: String) {
self.name = name
}
}
// MageChamp ์ฑํผ์ธ ํด๋์ค
class MageChamp: 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
}
}
let apChamp = ApChamp(name: "์์นผ๋ฆฌ")
let adChamp = AdChamp(name: "์๋ค")
let supportChamp = SupportChamp(name: "์นด๋ฅด๋ง")
let mageChamp = MageChamp(name: "์กฐ์ด")
// * ์ฌ๊ธฐ๋ง ๋ฐ๊ฟ์ฃผ๋ฉด ๋ชจ๋ ์์ ์ฌํญ์ด ์๋ฃ๋๋ค.
let ksw = Player(apMost: supportChamp, adMost: adChamp)
// let ksw = Player(apMost: mageChamp, adMost: adChamp)
print(ksw.apMost.name) // ์นด๋ฅด๋ง
print(ksw.adMost.name) // ์๋ค
๋๊ฐ์ด Champ ํ๋กํ ์ฝ์ ์ค์ํ๋ Support, Mage ํด๋์ค๋ฅผ ์ธ๋ถ์์ ์์ฑํ๊ณ , ์ฃผ์ ์ํค๋ ๋ถ๋ถ๋ง ๋ณ๊ฒฝํ๋ฉด ๋ชจ๋ ์์ ์ฌํญ์ด ์๋ฃ๋๋ค. ์ด๋ฒ ๋ฒ์ ์๋ supportChamp ์ฃผ์ ํ๊ณ , ๋ฐ๊พธ๋ผ๊ทธ๋ฌ๋ฉด ๊ทธ๋ ๊ทธ๋ ํ๋กํ ์ฝ์ ์ค์ํ๋ ํด๋์ค๋ก ๊ต์ฒด๋ง ํด์ฃผ๋ฉด ๋๋ค. Player ํด๋์ค ๋ด๋ถ์ ์ด๋ค๊ฒ๋ ์์ ํ ํ์๊ฐ ์๋ค.
ํ์ฌ ์์๋ ์ฑํผ์ธ ํด๋์ค๋ค์ด ๊ฐ๋ ํ๋กํผํฐ๊ฐ name ๋ฟ์ด์ง๋ง, ์ด๋ฐ ๊ฒฝ์ฐ๋ฅผ ์๊ฐํด๋ณด๋ฉด ํจ๊ณผ๊ฐ ๋ ๊ฐ๋ ฅํ๋ค.
// Champ ๊ฐ ๊ฐ๋ ์์๊ฐ ํจ์ฌ ๋์ด๋ ๊ฒฝ์ฐ
protocol Champ: AnyObject {
var name: String { get }
var HP: Int { get }
var MP: Int { get }
var ๊ณต๊ฒฉ๋ ฅ: Int { get }
var ๋ฐฉ์ด๋ ฅ: Int { get }
...
func normalSkill()
func ultimateSkill()
...
}
Player ํด๋์ค ๋ด๋ถ์์ ํ๋กํ ์ฝ์ ํ๋กํผํฐ, ํ๋กํ ์ฝ์ ๋ฉ์๋๋ฅผ ์ฌ์ฉํ๋ค๋ฉด ์ธ๋ถ ๊ฐ์ฒด ์ธ์คํด์ค๋ฅผ ๊ต์ฒดํด๋ ์๋ฌด๋ ์ง ์๊ฒ ์ ๋์ํ๊ฒ ๋๋ค.
์ด๋ฐ ๊ฒฝ์ฐ๋ฅผ ์์กด์ฑ์ด ๋ฎ์์ก๋ค. ๊ฒฐํฉ๋๊ฐ ๋ฎ์์ก๋ค๋ผ๊ณ ํํํ๋ค.
์ฌํ ์ค๋ช ํ IOC, ์์กด ์ญ์ ์ ๊ตฌํํ๋ ํ๋ ์์ํฌ๋ฅผ IOC Container ๋ผ๊ณ ํ๋ค.
์ ์ด๊ถ์ ํ๋ ์์ํฌ๊ฐ ๊ฐ์ ธ๊ฐ๊ฒ ๋๋ ๊ฒ.
์ด๋ฅผ ์ํ ํ๋ ์์ํฌ๋ก Swinject ๊ฐ ์๋ค.
Swinject : Swift ์์ DI (์์กด์ฑ ์ฃผ์ )์ ์ํ ํ๋ ์์ํฌ. ๊ฐ์ฒด ์์ฒด๊ฐ ์๋๋ผ ํ๋ ์์ํฌ์ ์ํด ๊ฐ์ฒด์ ์์กด์ฑ์ด ์ฃผ์ ๋๋๋ก ํ๋ค.
In the pattern, Swinject helps your app split into loosely-coupled components, which can be developed, tested and maintained more easily.
โ DI ํจํด์์ Swinject๋ ์ฑ์ด ๋์จํ๊ฒ ๊ฒฐํฉ๋ ๊ตฌ์ฑ ์์๋ก ๋ถํ ๋ ์ ์๋๋ก ๋์์ฃผ๋ฉฐ, ์ด๋ ๋ณด๋ค ์ฝ๊ฒ ๊ฐ๋ฐ, ํ ์คํธ ๋ฐ ์ ์ง ๊ด๋ฆฌํ ์ ์์ต๋๋ค.
pod 'Swinject'
https://github.com/Swinject/Swinject
https://doodledevnote.tistory.com/30
๊ณต์ ๊นํ๋ธ ์์ ๋ฅผ ์ํํด๋ดค๋ค.
1. AppDelegate.swift ์ Container ๋ฅผ ์์ฑํ๋ค. ๊ทธ๋ฆฌ๊ณ Container ์ ํ๋กํ ์ฝ๊ณผ ํด๋์ค๋ฅผ ๋ฑ๋กํ๋ค.
- ์์กด์ฑ ์ฃผ์ ์ ํด๋์ค์ ์ธ๋ถ์์ ํ๋ค. ๋ฐ๋ผ์ ViewController ํด๋์ค์ ์ธ๋ถ์ธ AppDelegate ์ Swinject Container ๋ฅผ ์ ์ธํด์คฌ๋ค.
- register : Container ์ ์ฌ์ฉํ ํ๋กํ ์ฝ ๋ฑ๋ก
- resolve : ํด๋์ค ์ฌ์ฉ
import UIKit import Swinject import Foundation protocol Animal { var name: String? { get } } class Cat: Animal { let name: String? init(name: String?) { self.name = name } } @main class AppDelegate: UIResponder, UIApplicationDelegate { let container: Container = { let container = Container() // Cat ์ด๋ผ๋ ํด๋์ค์ Animal ํ๋กํ ์ฝ์๊ฒ ์ ์ด๊ถ์ ๋๊ธฐ๋ ๋์์, // ์์กด์ฑ์ ์ฃผ์ ํ๋ค. container.register(Animal.self) { _ in Cat(name: "Mimi") } // ViewController ํด๋์ค๋ฅผ ๋ฑ๋กํ๋ค. container.register(ViewController.self) { resolver in let controller = ViewController() controller.animal = resolver.resolve(Animal.self) return controller } return container }() ... }
2. SceneDelegate.swift ์์ ๋ฑ๋กํ๋ ViewController ๋ฅผ ์ฌ์ฉํ๋ค.
class SceneDelegate: UIResponder, UIWindowSceneDelegate { var window: UIWindow? let appDelegate = UIApplication.shared.delegate as! AppDelegate func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { guard let scene = (scene as? UIWindowScene) else { return } self.window = UIWindow(windowScene: scene) self.window?.rootViewController = appDelegate.container.resolve(ViewController.self) self.window?.makeKeyAndVisible() }
3. ViewController.swift ์์ ๋ฑ๋กํ๋ Animal ์ ์ฌ์ฉํ๋ค.
import UIKit class ViewController: UIViewController { var animal: Animal? override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view. print("Animal name is \(animal!.name!)") } }
+ ๊ฐ์ ํ๋กํ ์ฝ์ ์ค์ํ๋ ํด๋์ค๊ฐ ์ฌ๋ฌ ๊ฐ์ผ ๊ฒฝ์ฐ
- Animal ํ๋กํ ์ฝ์ ์ค์ํ๋ Cat, Dog ํด๋์ค๊ฐ ์์ ๋์ ์์.
- AppDelegate.swift
import UIKit import Swinject import Foundation protocol Animal { var name: String? { get } var cry: String? { get } } class Cat: Animal { let name: String? let cry: String? init(name: String?) { self.name = name self.cry = "Meow" } } class Dog: Animal { let name: String? let cry: String? init(name: String?) { self.name = name self.cry = "Bark" } } @main class AppDelegate: UIResponder, UIApplicationDelegate { let container: Container = { let container = Container() container.register(Cat.self) { _ in Cat(name: "Mimi")} container.register(Dog.self) { _ in Dog(name: "Popi")} container.register(ViewController.self) { resolver in let controller = ViewController() controller.cat = resolver.resolve(Cat.self) controller.dog = resolver.resolve(Dog.self) return controller } return container }() ... }
- ViewController.swift
import UIKit class ViewController: UIViewController { var cat: Cat? var dog: Dog? override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view. print("Cat name is \(cat!.name!)") print(cat!.cry!) print("Dog name is \(dog!.name!)") print(dog!.cry!) } }
์ด ๊ธ์ ๊ฒ์ํ์ง ์ฝ 1๋ ๋ค์ธ ํ์ฌ, DI / Swinject ์ ๋ํ ์ดํด๋๊ฐ ๋ ๋์์ก๊ณ , ์ถ๊ฐ์ ์ธ ๋ด์ฉ์ ์์ฑํ๊ณ ์ถ๋ค.
๊ฐ์ฅ ํ๊ณ ์ถ์ ๋ง์ ๊ฒฐ๋ก ๋ถํฐ ์ด์ผ๊ธฐํ๋ฉด,
์๋ฐํ ๋ฐ์ง๋ฉด, Swinject๋ "DI ๋ผ์ด๋ธ๋ฌ๋ฆฌ"๊ฐ ์๋๋ค.
์ ์๋ง ๋๊ณ ์๊ธฐํ๋ฉด "Service Locator"์ ๋ ๊ฐ๊น๋ค.Swinject๋ Service Locator ๋ก์จ, DI ๋ก์จ ๋ชจ๋ ์ฌ์ฉ๋ ์ ์๋ค.
๐ Service Locator ๋ผ๊ณ ๋งํ ๊ทผ๊ฑฐ : Dependency Injection in iOS with Swinject
์ ๋งํฌ์ ๋ง์ ์ธ์ฉํด์ ๊ฐ์ ธ์ค๋ฉด ๋ค์๊ณผ ๊ฐ๋ค.
But Swinject is not a Dependecy Injection Framework. Sorry for the shock. Swinject is under the hood just a simple Service Locator and no real dependecy injection framework, what injects the dependecies already during the compile time. I know what you are thinking. You feel cheated. But hey: Swinject does generally a similar job as real DI framework does.
Swinject๋ ์์กด์ฑ ์ฃผ์ ํ๋ ์์ํฌ๊ฐ ์๋๋๋ค. ์ถฉ๊ฒฉ์ ์ค์ ๋ฏธ์ํฉ๋๋ค. Swinject๋ ๋จ์ํ ์๋น์ค ๋ก์ผ์ดํฐ์ผ ๋ฟ ์ค์ ์ข ์์ฑ ์ฃผ์ ํ๋ ์์ํฌ๋ ์์ผ๋ฉฐ ์ปดํ์ผ ์๊ฐ ๋์ ์ด๋ฏธ ์ข ์์ฑ์ ์ฃผ์ ํฉ๋๋ค. ๋น์ ์ด ์ด๋ป๊ฒ ์๊ฐํ๊ณ ์๋์ง ์๊ณ ์์ด์. ์์๋ค๊ณ ๋๋ผ์๋๊ตฐ์. ํ์ง๋ง Swinject๋ ์ผ๋ฐ์ ์ผ๋ก ์ค์ DI ํ๋ ์์ํฌ์ ์ ์ฌํ ์์ ์ ์ํํฉ๋๋ค.
Service Locator : ๋๊ตฐ๊ฐ ํ์๋ก ํ๋ ๊ฐ์ฒด๋ฅผ ์ ๊ณตํด์ฃผ๋ ์ญํ ์ ์ํํ๋ ๊ฐ์ฒด๋ฅผ ๋งํ๋ค.
Swinject์ Container์ registerํ๋ ๊ณผ์ ์ Service Locator์๊ฒ ๊ฐ์ฒด๋ฅผ ๋ฑ๋กํ๋ ๊ณผ์ ์ด๊ณ , resolveํด์ ์ฌ์ฉํ๋ ๊ฒ์ ํ์ํ ๊ฐ์ฒด๋ฅผ ์ ๊ณต๋ฐ์ ์ฌ์ฉํ๋ ๊ณผ์ ์ด๋ค.
๊ทธ๋์ Swinject์ register / resolve ๋ฉ์๋๋ง ๋จ์ง ์ฌ์ฉํ๋ ๊ฒ์ DI๋ก์จ ์ฌ์ฉํ ๊ฒ์ด ์๋๋ผ Service Locator๋ก์จ ์ฌ์ฉํ ๊ฒ์ด๋ค.
ํ๋กํ ์ฝ์ ๋๊ณ , ์ด๋์ ๋ผ์ด์ ๋ setter๋ฅผ ํตํด์ ๊ฐ์ฒด ๋ด๋ถ๋ก ์ฃผ์ ์ํค๋ ๊ณผ์ ์ด ์์ด์ผ๋ง DI๋ก์จ ํ์ฉ์ด ๋ ๊ฒ์ด๋ผ๊ณ ํ ์ ์๋ค.
๐ Service Locator์ DI ๊ฐ๋ ์ ์์ธํ ์ค๋ช ํ ๊ธ โ Service Locator vs DI
Swinject์์ ์ ๊ณตํ๋ Assembly๋ ๋น์ทํ ์ฑ ์์ ๊ฐ๋ ๊ฐ์ฒด๋ผ๋ฆฌ ๋ชจ์์ ๊ด๋ฆฌํ๊ณ ๋ฑ๋กํ ์ ์๋๋ก ๋๋๋ค. ์๋ฅผ๋ค์ด ํด๋ฆฐ ์ํคํ ์ฒ๋ฅผ ์ค์ํด Domain ๋ชจ๋, Data ๋ชจ๋์ด ์๋ ์ํ๋ผ๊ณ ํด๋ณด์.
[์๋ง ๋ ์จ ์ฑ]์ ์๋ก ๋ค์ด๋ณด๋ฉด, Domain ๋ชจ๋์๋ ViewModel์์ ๋ ์จ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ฌ ์ ์๋๋ก ๋๋ UseCase ๊ฐ ์๊ณ , Data ๋ชจ๋์๋ ๋ ์จ ๋ฐ์ดํฐ๋ฅผ ์๋ฒ์์ ์ง์ ๊ฐ์ ธ์ฌ WeatherDataSource ์ WeatherRepository ๊ฐ ์๋ค.
๊ฐ์ฅ ์์ ๋ชจ๋์ธ App๋ชจ๋์์ DI ์ฃผ์ ์ ํด์ผํ๊ธฐ ๋๋ฌธ์, App๋ชจ๋์ DomainAssembly.swift์ DataAssembly.swift๋ฅผ ์ ์ํ๋ค.
// DomainAssembly.swift
import Domain
import Swinject
public struct DomainAssembly: Assembly {
public func assemble(container: Container) {
container.register(WeatherUseCase.self) { resolver in
let repository = resolver.resolve(WeatherRepository.self)!
return DefaultWeatherUseCase(repository: repository)
}
}
------------------------------------------------------------
// DataAssembly.swift
import Swinject
import Domain
import Data
public struct DataAssembly: Assembly {
public func assemble(container: Container) {
container.register(WeatherDataSource.self) { _ in
return DefaultWeatherDataSource()
}
container.register(WeatherRepository.self) { resolver in
let dataSource = resolver.resolve(WeatherDataSource.self)!
return DefaultWeatherRepository(dataSource: dataSource)
}
}
}
๊ทธ๋ฆฌ๊ณ ์ด๋ ๊ฒ ๋ฑ๋กํ Assembly ๋ค์ ๋ชจ์์ ์ฑ ์คํ ์์ ์์ ์ ํจ๊ป ๋ฑ๋กํ๋ค.
// SceneDelegate.swift
import Swinject
import UIKit
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
private let injector: Injector = DependencyInjector(container: Container())
private var appCoordinator: DefaultAppCoordinator?
func scene(
_ scene: UIScene,
willConnectTo session: UISceneSession,
options connectionOptions: UIScene.ConnectionOptions
) {
guard let scene = (scene as? UIWindowScene) else { return }
let navigationController = UINavigationController()
window = .init(windowScene: scene)
window?.rootViewController = navigationController
window?.makeKeyAndVisible()
appCoordinator = DefaultAppCoordinator(dependency: .init(navigationController: navigationController, injector: injector))
// * Service Locator ์ ์ฌ์ฉํ ๊ฐ์ฒด๋ฅผ ๋ชจ์๋ Assembly ๋ค์ ๋ชจ๋ ๋ฑ๋ก.
injector.assemble([DataAssembly(),
DomainAssembly(),
HomeAssembly(),
ForecastAssembly(),
SearchAssembly()])
appCoordinator?.start()
}
...
}
๋ง์ฝ ์๋ฒ์์ ๊ฐ์ ธ์ค๋ Weather ๋ฐ์ดํฐ๋ฅผ ๋ก์ปฌ์์ ๊ฐ์ ธ์ค๋๋ก ์๊ตฌ์ฌํญ์ด ๋ณ๊ฒฝ๋์๋ค๊ณ ๊ฐ์ ํด๋ณด์ (๊ธฐ๋ฅ์ ์ผ๋ก ๊ทธ๋ด์ผ์ด ์๊ธธ ๊ฒ ๊ฐ์ง๋ ์์ง๋ง). ๊ทธ๋ ๋ค๋ฉด WeatherDataSource ํ๋กํ ์ฝ์ ๋ฐ๋ฅด๋ LocalWeatherDataSource๋ฅผ ์ปค์คํ ํด์ ์์ฑํด๋ด๊ณ , ์์์ ๋ณธ DataAssembly ์์ ์ฝ๋๋ฅผ ๋ค์๊ณผ ๊ฐ์ด ์์ ํ๋ฉด ๋๋ค.
public struct DataAssembly: Assembly {
public func assemble(container: Container) {
container.register(WeatherDataSource.self) { _ in
// return DefaultWeatherDataSource()
return LocalWeatherDataSource()
}
์ด๋ ๊ฒ DI๋ฅผ ํ์ฉํ๋ฉด, ๋จ ํ ์ค์ ์์ ์ผ๋ก ์ถ๊ฐ์ ์ธ ์ฐ์์ ์ธ ์์ ์์ด ๋ชจ๋ ์์ ์ ๋๋ผ ์ ์๊ฒ ๋๋ค.
https://github.com/Swinject
https://rma7.tistory.com/69
https://kkimin.tistory.com/47
https://medium.com/@jang.wangsu/di-dependency-injection-์ด๋-1b12fdefec4f