[Swift] DI ์™€ Swinject

๊น€์ƒ์šฐยท2022๋…„ 5์›” 2์ผ
12

๐Ÿง‘๐Ÿปโ€๐Ÿ’ป ์†Œ๋งˆ ์ข…์ฐฌ ๋ฉ˜ํ† ๋‹˜์˜ ๋ฉ˜ํ† ๋ง ํŒ”๋กœ์šฐ์—…์„ ์œ„ํ•ด ์ •๋ฆฌํ•œ ๊ธ€
(2022๋…„์— ์ž‘์„ฑํ•œ ๊ฒŒ์‹œ๊ธ€์ด์—ˆ๋Š”๋ฐ, 2023๋…„์— ์กฐ๊ธˆ ์ˆ˜์ •ํ•˜๊ณ  ๋ณด์™„ํ–ˆ์Šต๋‹ˆ๋‹ค.)

๐Ÿญ 2023.04 ์ถ”๊ฐ€ ๋‚ด์šฉ : DI์™€ Swinject๋ฅผ ๋” ๊ณต๋ถ€ํ•ด์„œ Toy Project์— ์ ์šฉํ•ด๋ดค์Šต๋‹ˆ๋‹ค. ์•„๋ž˜ ์ฝ”๋“œ๋ฅผ ์ฐธ๊ณ ํ•˜๋ฉด Swinject์— ๋Œ€ํ•œ ์ดํ•ด๋„๋ฅผ ๋” ๋†’์ด๋Š” ๋ฐ ์ฐธ๊ณ ๊ฐ€ ๋  ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค.

๐Ÿ”— DI, Swinject ๋ฅผ ์ ์šฉํ•œ [์†Œ๋งˆ ๋‚ ์”จ ์•ฑ] : https://github.com/heyksw/Soma-WeatherApp

  • Projectsโ†’Appโ†’Sourcesโ†’DI
  • Projectsโ†’Appโ†’Sourcesโ†’Applicationโ†’SceneDelegate.swift

์ด ๋‘ ๊ตฐ๋ฐ๋ฅผ ์‚ดํŽด๋ณด๋ฉด ๋ฉ๋‹ˆ๋‹ค.


DI (Dependency Injection)

๋ฉด์ ‘์—์„œ "DI๊ฐ€ ๋ญ๋ƒ"๊ณ  ํ•˜๋ฉด ๋‚˜๋Š” ์ด๋ ‡๊ฒŒ ๋Œ€๋‹ตํ•  ๊ฑฐ๋‹ค.

"Dependency Injection์€ ํด๋ž˜์Šค ๋‚ด๋ถ€์—์„œ ํ•„์š”ํ•œ ๊ฐ์ฒด์˜ ์ธ์Šคํ„ด์Šค๋ฅผ, ํด๋ž˜์Šค ๋‚ด๋ถ€์—์„œ ์ƒ์„ฑํ•˜๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋ผ ์™ธ๋ถ€์—์„œ ์ƒ์„ฑํ•œ ๋’ค ์ด๋‹ˆ์…œ๋ผ์ด์ € ๋˜๋Š” setter๋ฅผ ํ†ตํ•ด ๋‚ด๋ถ€๋กœ ์ฃผ์ž…๋ฐ›๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์ด ๋•Œ ์ด๋‹ˆ์…œ๋ผ์ด์ €์˜ ํƒ€์ž…์€ ํ”„๋กœํ† ์ฝœ์„ ํ™œ์šฉํ•ด์„œ ๋‚ด๋ถ€์—์„œ๋Š” ํ”„๋กœํ† ์ฝœ ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค."

Dependency Injection์„ ์ง์—ญํ•˜๋ฉด, ์˜์กด์„ฑ ์ฃผ์ž…์ด๋‹ค.
DI ๋Š” "์˜์กด์„ฑ" ์„ ํด๋ž˜์Šค์— "์ฃผ์ž…" ์‹œํ‚ค๋Š” ๊ฒƒ์ด๊ณ , "์˜์กด์„ฑ ๋ถ„๋ฆฌ"์˜ ์กฐ๊ฑด์„ ๋งŒ์กฑํ•ด์•ผ ํ•œ๋‹ค.

๐Ÿง ์ •ํ™•ํ•œ ์ดํ•ด๋ฅผ ์œ„ํ•ด ๋‹ค์Œ 4๊ฐ€์ง€ ๊ฐœ๋…์„ ์ˆœ์„œ๋Œ€๋กœ ์ •๋ฆฌํ–ˆ๋‹ค.

  1. ์˜์กด์„ฑ
  2. ์ฃผ์ž…
  3. ์˜์กด์„ฑ ๋ถ„๋ฆฌ
  4. IOC Container

1. ์˜์กด์„ฑ (Dependency)

ํด๋ž˜์Šค 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() ์ธ์Šคํ„ด์Šค๋ฅผ ์ƒ์„ฑํ•˜๊ณ  ์žˆ๋‹ค๋Š” ๊ฑธ ์ฃผ๋ชฉํ•ด์•ผํ•œ๋‹ค.


2. ์ฃผ์ž… (Injection)

์œ„์˜ 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๋ฅผ ์˜์กดํ•œ๋‹ค๊ณ  ์ƒ๊ฐํ•  ์ˆ˜ ์žˆ๋‹ค.

์—ฌํŠผ ์ด๋ ‡๊ฒŒ ํ•ด์„œ "์˜์กด์„ฑ"์„ "์ฃผ์ž…"ํ–ˆ๋‹ค.


3. ์˜์กด์„ฑ ๋ถ„๋ฆฌ

์œ„์˜ ์ฝ”๋“œ๊นŒ์ง€ ์˜์กด์„ฑ ์ฃผ์ž…์„ ์‚ดํŽด๋ดค๋‹ค. ํ•˜์ง€๋งŒ ์ผ๋ฐ˜์ ์œผ๋กœ ์˜์กด์„ฑ์„ ์ฃผ์ž…์‹œํ‚จ ๊ฒƒ ๋งŒ์œผ๋กœ DI ๋ผ๊ณ  ๋ถ€๋ฅด์ง€๋Š” ์•Š๋Š”๋‹ค.

์˜์กด์„ฑ ๋ถ„๋ฆฌ์˜ ์กฐ๊ฑด์„ ๋งŒ์กฑ์‹œ์ผœ์•ผ DI ๋ผ๊ณ  ํ•œ๋‹ค.
๊ทธ๋ฆฌ๊ณ  ์˜์กด์„ฑ ๋ถ„๋ฆฌ๋Š” "์˜์กด ์—ญ์ „์˜ ์›์น™"์„ ๊ธฐ๋ฐ˜์œผ๋กœ ๋ถ„๋ฆฌ๋ฅผ ์‹คํ–‰ํ•œ๋‹ค.

SOLID 5์›์น™ ์ค‘ Dependency Inversion Principle ์„ ์˜๋ฏธํ•œ๋‹ค.

๐Ÿค“ DIP ์›์น™์ด๋ž€

  • ์˜์กด ๊ด€๊ณ„๋ฅผ ๋งบ์„ ๋•, ๋ณ€ํ™”ํ•˜๊ธฐ ์‰ฌ์šด ๊ฒƒ๋ณด๋‹จ ๋ณ€ํ™”ํ•˜๊ธฐ ์–ด๋ ค์šด ๊ฒƒ์— ์˜์กดํ•ด์•ผ ํ•œ๋‹ค๋Š” ์›์น™.
  • ์—ฌ๊ธฐ์„œ ๋ณ€ํ™”ํ•˜๊ธฐ ์–ด๋ ค์šด ๊ฒƒ์ด๋ž€ ์ถ”์ƒ ํด๋ž˜์Šค๋‚˜ ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๋งํ•˜๊ณ  ๋ณ€ํ™”ํ•˜๊ธฐ ์‰ฌ์šด ๊ฒƒ์€ ๊ตฌ์ฒดํ™”๋œ ํด๋ž˜์Šค๋ฅผ ์˜๋ฏธ.
  • ๋”ฐ๋ผ์„œ 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 ํด๋ž˜์Šค ๋‚ด๋ถ€์—์„œ ํ”„๋กœํ† ์ฝœ์˜ ํ”„๋กœํผํ‹ฐ, ํ”„๋กœํ† ์ฝœ์˜ ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค๋ฉด ์™ธ๋ถ€ ๊ฐ์ฒด ์ธ์Šคํ„ด์Šค๋ฅผ ๊ต์ฒดํ•ด๋„ ์•„๋ฌด๋ ‡์ง€ ์•Š๊ฒŒ ์ž˜ ๋™์ž‘ํ•˜๊ฒŒ ๋œ๋‹ค.

์ด๋Ÿฐ ๊ฒฝ์šฐ๋ฅผ ์˜์กด์„ฑ์ด ๋‚ฎ์•„์กŒ๋‹ค. ๊ฒฐํ•ฉ๋„๊ฐ€ ๋‚ฎ์•„์กŒ๋‹ค๋ผ๊ณ  ํ‘œํ˜„ํ•œ๋‹ค.


4. IOC Container

์—ฌํƒœ ์„ค๋ช…ํ•œ IOC, ์˜์กด ์—ญ์ „์„ ๊ตฌํ˜„ํ•˜๋Š” ํ”„๋ ˆ์ž„์›Œํฌ๋ฅผ IOC Container ๋ผ๊ณ  ํ•œ๋‹ค.

์ œ์–ด๊ถŒ์„ ํ”„๋ ˆ์ž„์›Œํฌ๊ฐ€ ๊ฐ€์ ธ๊ฐ€๊ฒŒ ๋˜๋Š” ๊ฒƒ.
์ด๋ฅผ ์œ„ํ•œ ํ”„๋ ˆ์ž„์›Œํฌ๋กœ Swinject ๊ฐ€ ์žˆ๋‹ค.


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'

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!)
    }
}


๐Ÿฉ 2023.04 ์ถ”๊ฐ€ ๋‚ด์šฉ

์ด ๊ธ€์„ ๊ฒŒ์‹œํ•œ์ง€ ์•ฝ 1๋…„ ๋’ค์ธ ํ˜„์žฌ, DI / Swinject ์— ๋Œ€ํ•œ ์ดํ•ด๋„๊ฐ€ ๋” ๋†’์•„์กŒ๊ณ , ์ถ”๊ฐ€์ ์ธ ๋‚ด์šฉ์„ ์ž‘์„ฑํ•˜๊ณ  ์‹ถ๋‹ค.

๊ฐ€์žฅ ํ•˜๊ณ  ์‹ถ์€ ๋ง์„ ๊ฒฐ๋ก ๋ถ€ํ„ฐ ์ด์•ผ๊ธฐํ•˜๋ฉด,

์—„๋ฐ€ํžˆ ๋”ฐ์ง€๋ฉด, Swinject๋Š” "DI ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ"๊ฐ€ ์•„๋‹ˆ๋‹ค.
์ •์˜๋งŒ ๋†“๊ณ  ์–˜๊ธฐํ•˜๋ฉด "Service Locator"์— ๋” ๊ฐ€๊น๋‹ค.

Swinject๋Š” Service Locator ๋กœ์จ, DI ๋กœ์จ ๋ชจ๋‘ ์‚ฌ์šฉ๋  ์ˆ˜ ์žˆ๋‹ค.


DI vs Service Locator

๐Ÿ”— 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


Assembly

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๋ฅผ ํ™œ์šฉํ•˜๋ฉด, ๋‹จ ํ•œ ์ค„์˜ ์ˆ˜์ •์œผ๋กœ ์ถ”๊ฐ€์ ์ธ ์—ฐ์‡„์ ์ธ ์ˆ˜์ •์—†์ด ๋ชจ๋“  ์ž‘์—…์„ ๋๋‚ผ ์ˆ˜ ์žˆ๊ฒŒ ๋œ๋‹ค.


Reference

https://github.com/Swinject
https://rma7.tistory.com/69
https://kkimin.tistory.com/47
https://medium.com/@jang.wangsu/di-dependency-injection-์ด๋ž€-1b12fdefec4f

profile
์•ˆ๋…•ํ•˜์„ธ์š”, iOS ์™€ ์•Œ๊ณ ๋ฆฌ์ฆ˜์— ๋Œ€ํ•œ ๊ธ€์„ ์”๋‹ˆ๋‹ค.

0๊ฐœ์˜ ๋Œ“๊ธ€