์•„๋ฌด ์ƒ๊ฐ์—†์ด ์ฝ”๋”ฉํ•˜๋‹ค๊ฐ€(โ€ฆ) ์˜์กด์„ฑ ์ฃผ์ž…์„ ๊ทธ๋ƒฅ property ์ฃผ์ž…์œผ๋กœ ํ•˜๊ณ  ์žˆ์—ˆ๋‹ค. ๋‹น์—ฐํžˆ Storyboard๋ฉด ์•ˆ๋˜์ง€ ์•Š์•˜์—ˆ๋‚˜? ํ•˜๋Š” ๋ฌด์ง€์„ฑ ์ฝ”๋“œ์งˆ์„ ํ•˜๋‹ค ์ง€์ ๋ฐ›๊ณ  ๋– ์˜ฌ๋ž๋‹ค.. ๊ณผ๊ฑฐ์˜ ๋‚ด๊ฐ€ ์ด๋ฏธ ๊ทธ์ง“๊ฑฐ๋ฆฌ๋ฅผ ๊ณต๋ถ€ํ–ˆ๋‹ค๋Š” ๊ฒƒ์„!

๋ธ”๋กœ๊ทธ ์ด์ „ํ•˜๋ฉด์„œ ๋‹ค์‹œ ์ •๋ฆฌํ•ด๋‘ฌ์•ผ ๊ฒ ๋‹ค๋Š” ์ƒ๊ฐ์„ ํ–ˆ๋‹ค. ์—ญ์‹œ ์ฐธ ์ž˜ ์žŠ์–ด๋ฒ„๋ฆฐ๋‹ค..

Trial & Error in Dependency Injection

์ผ๋‹จ ์˜์กด์„ฑ ์ฃผ์ž…์— ์–ด๋–ค ๋ฐฉ์‹์ด ์žˆ๋Š”์ง€ ๋ถ€ํ„ฐ ์•Œ์•„๋ณด์ž. ๋‹จ๊ณ„๋ณ„๋กœ ์‹œํ–‰์ฐฉ์˜ค๋ฅผ ๊ฒช์—ˆ๋˜ ๊ฒƒ์„ ๋‚˜์—ดํ•ด๋ณด๊ฒ ๋‹ค. ๊ทธ ๊ณผ์ •์—์„œ ์–ด๋–ค ์ ์ด ์ข‹๊ณ  ๋‚˜์œ์ง€์— ๋Œ€ํ•ด ์ดํ•ดํ•˜๋Š” ๊ฒƒ์ด ์ค‘์š”ํ•˜๋‹ค. ์ฒ˜์Œ ๋ฐฐ์šธ ๋•Œ ํ–ˆ๋˜ ์ถ”์–ต๊นŒ์ง€ ๊ฐ™์ด ์ •๋ฆฌํ•ด๋ณด๊ฒ ๋‹ค.

Bad Way

์ฒ˜์Œ์— ํ•œ ๋ฐฉ๋ฒ•์ด๋‹ค. ๊ทธ ๋‹น์‹œ์— ๋‚˜๋Š” ์˜์กด์„ฑ ์ฃผ์ž…์ด ๋ญ”์ง€๋„ ๋ชฐ๋ž๋˜ ๊ฒƒ ๊ฐ™๋‹ค. ๊ทธ๋ƒฅ ๋กœ์ง์„ ๋ถ„๋ฆฌํ•˜๊ธฐ๋งŒ ํ•˜๋ฉด ๋‹ค๋ผ ์ƒ๊ฐํ–ˆ๋‹ค. ํ•˜์ง€๋งŒ ์ด๋Ÿฐ์‹์˜ ์ ‘๊ทผ์€ ์ •๋ง ๋‹จ์ˆœํžˆ โ€œ๋ถ„๋ฆฌโ€์—๋งŒ ์˜๋ฏธ๋ฅผ ๋‘์—ˆ์„ ๋ฟ, ๊ธฐ์กด ์ฝ”๋“œ์™€ ๋‹ฌ๋ผ์ง€๋Š” ๊ฒƒ์ด ์—†๋‹ค. ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ๋นต์ ์ด๋‹ค.

Property & Method Injection

์ด์ œ๋ถ€ํ„ฐ ๊ทธ๋‚˜๋งˆ injection์ด๋ผ ๋ถ€๋ฅผ ์ˆ˜ ์žˆ๋Š” ์ˆ˜์ค€์˜ ๋ฐฉ๋ฒ•์ด๋‹ค. ์™ธ๋ถ€์—์„œ ์‚ฌ์šฉํ•˜๊ณ ์ž ํ•˜๋Š” ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•˜๊ณ  ์ด๋ฅผ ๋ฐ€์–ด๋„ฃ์–ด ์ตœ์ข… ๊ฐ์ฒด๋ฅผ ๋งŒ๋“ค์–ด ๋‚ธ๋‹ค. ์ด ๋•Œ, property injection์€ ๋ง๊ทธ๋Œ€๋กœ instance์˜ property์— ์ ‘๊ทผํ•ด์„œ ๋„ฃ์–ด๋ฒ„๋ฆฌ๋Š” ๊ฒƒ์„ ๋งํ•œ๋‹ค. method๋Š” method๋กœ ์ด๋ฅผ ๊ฐ์‹ธ๋Š” ๊ฒƒ์„ ๋งํ•œ๋‹ค.

์ฐจ์ด๊ฐ€ ์žˆ๋‹ค๋ฉด, property injection์˜ ๊ฒฝ์šฐ property์˜ access control์„ internal๋กœ ์—ด์–ด๋‘์–ด์•ผ ํ•œ๋‹ค๋Š” ์ ์ด๋‹ค. ๋ฐ˜๋Œ€๋กœ method injection์€ property์˜ access๋Š” privateํ•˜๊ฒŒ ๋‘˜ ์ˆ˜ ์žˆ๋‹ค. ํ•˜์ง€๋งŒ ํ•จ์ˆ˜ ๊ธธ์ด๊ฐ€ ๊ธธ์–ด์ง„๋‹ค. ๊ทธ๋ž˜์„œ ์ด ๋ถ€๋ถ„์€ ๊ฐœ์ธ์˜ ํŒ๋‹จ์— ๋งก๊ฒจ์•ผ ํ•œ๋‹ค.

๊ทธ๋Ÿผ ์ด ๋ฐฉ์‹์˜ ๋‹จ์ ์— ๋Œ€ํ•ด ์ƒ๊ฐํ•ด๋ณด์ž. ์ผ๋‹จ ์ปดํŒŒ์ผ ํƒ€์ž„์— ์ฃผ์ž… ์—ฌ๋ถ€ ํ™•์ธ์ด ๋ถˆ๊ฐ€ํ•˜๋‹ค. ์ด๊ฒŒ ๋งน์ ์ธ๋ฐ, ์‚ฌ์‹ค ๊ฐœ๋ฐœ์ž๋Š” ์ปดํŒŒ์ผ ํƒ€์ž„์— ๋Œ€๋ถ€๋ถ„ ์žกํ˜€์ฃผ๋ฉด ๋•กํ๋‹ค. ๋Ÿฐํƒ€์ž„์— ์—๋Ÿฌ๋‚˜๋ฉด ์•„์šฐ ๋จธ๋ฆฌ์•„ํ”„๋‹ค.. ๊ทธ๋ž˜์„œ ์ด ๋ฐฉ์‹์ด ๋งˆ์Œ์— ์•ˆ๋“ค์—ˆ์—ˆ๋‹ค.

Service Locator Pattern

๊ทธ ๋‹ค์Œ์—๋Š” ์ด๋…€์„์ด ์žˆ๋‹ค. ๊ฒฐ๋ก  ๋ถ€ํ„ฐ ๋งํ•˜๋ฉด ์ด๋…€์„์€ Anti Pattern์ด๋‹ค. ์•„์ง ์ œ๋Œ€๋กœ ๊ณต๋ถ€ํ•ด๋ณด์ง€ ๋ชปํ•ด ๋‚ด ์–ธ์–ด๋กœ ์ดํ•ด๋˜์ง€๋Š” ์•Š์•˜๋‹ค. ์ถ”๊ตฌ Tech Talk ๊ฒŒ์‹œ๊ธ€์—์„œ ๋งŒ๋‚˜๋ณด๊ฒŒ ๋ ๊ฑฐ๋‹ค.

๊ฐ„๋‹จํ•˜๊ฒŒ ๋งํ•ด๋ณด์ž๋ฉด, ๋ชจ๋“  ์˜์กด์„ฑ์„ ์•Œ๊ณ  ์žˆ๋Š” Locator ๊ฐ์ฒด์— ์˜์กด์„ฑ ์ฃผ์ž…์„ ์˜์กดํ•˜๋Š” (?) ๋ฐฉ๋ฒ•์ด๋‹ค. ๋ง์ด ์ฐธ.. ์‰ฝ๊ฒŒ ๋งํ•˜๋ฉด ์ด ๋…€์„ํ•œํ…Œ โ€œ์•ผ์•ผ ๋‚˜ ์˜์กด์„ฑ์ข€ ํ•ด๊ฒฐํ•ด์•ผ ํ•˜๋Š”๋ฐ, ๋ญ๋กœ ๋„ฃ์–ด์•ผ ํ•˜๋ƒ?โ€ ๋ผ๊ณ  ๋ฌผ์–ด๋ณด๋ฉด ์ด๋…€์„์ด ๋‹ต์„ ์•Œ๋ ค์ฃผ๋Š” ๊ฑฐ๋‹ค. ๊ทธ๋ ‡๋‹ค ์•ฝ๊ฐ„์˜ ์‹ฑ๊ธ€ํ†ค๊ณผ ๊ฐ™์€ ๋ƒ„์ƒˆ.. ์ „์—ญ ๊ฐ์ฒด๋‹ค.

์ผ๋‹จ ํ•ด๋‹น ๋ฐฉ๋ฒ•์„ ์‚ฌ์šฉํ–ˆ์„ ๋•Œ, ์ง๊ด€์ ์œผ๋กœ ๋ฐœ์ƒํ•˜๋Š” ๋ฌธ์ œ๋“ค์€ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๊ฒƒ๋“ค์ด ์žˆ๋‹ค.

  1. ์ƒ์„ฑ์ž๋งŒ ๋ณด๊ณ  ์˜์กด์„ฑ์„ ํŒŒ์•…ํ•  ์ˆ˜ ์—†๋‹ค.
  2. Locator์— ์˜์กดํ•˜๊ฒŒ ๋œ๋‹ค.
  3. Class์—์„œ ์š”์ฒญ์„ ํ•˜๋Š” ํ˜•ํƒœ์ด๊ธฐ ๋•Œ๋ฌธ์—, ์™ธ๋ถ€์—์„œ mock ๊ฐ์ฒด๋ฅผ ๊ฐˆ์•„ ๋ผ์›Œ์„œ ํ…Œ์ŠคํŠธ ํ•˜๋Š” ๊ฒƒ์ด ๋ถˆ๊ฐ€ํ•˜๋‹ค.

Initialization Injection

๊ฐ€์žฅ ๋งˆ์Œ์— ๋“œ๋Š” ๋ฐฉ์‹์ด๋‹ค. ์ƒ์„ฑ์ž๋งŒ ๋ณด๊ณ  ์˜์กด์„ฑ์„ ํŒŒ์•…ํ•  ์ˆ˜ ์žˆ๋‹ค. ๋‹ค๋งŒ, A, B ๋‘๊ฐœ ์ด์ƒ์˜ ๊ฐ์ฒด๊ฐ€ ์„œ๋กœ๋ฅผ ์•Œ์•„์•ผ ํ•˜๋Š” ์ƒํ™ฉ์ด๋ผ๋ฉด ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค. ์ด๋Ÿฐ ๊ฒฝ์šฐ๋Š” Property & Method injection์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ๊น”๋”ํ•  ์ˆ˜ ์žˆ๋‹ค.

let a = A(b: )
let b = B(a: )
 
// ..? ์ง„ํ–‰ํ•  ์ˆ˜๊ฐ€ ์—†๋‹ค. ์ˆœํ™˜์ด๋‹ค..
 
let a = A()
let b = B()
a.b = b
b.a = a
 
// Property & Method injection์ด ๊น”๋”ํ•˜๋‹ค.

์ƒ›๊ธธ๋กœ ๋น ์กŒ๋‹ค. ์ผ๋‹จ ์œ„์˜ ์‚ฌ์ง„์˜ ๊ฒฝ์šฐ๋Š” Code๋กœ๋งŒ injection์„ ํ•œ๋‹ค๋Š” ๊ฐ€์ •์—์„œ ์ง„ํ–‰ํ•œ ๊ฒƒ์ด๋‹ค. ํ•˜์ง€๋งŒ ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด Compile Time์— injection์„ ํ™•์ธํ•  ์ˆ˜ ์—†๋‹ค.

๊ทธ๋ž˜์„œ ์ด๋ ‡๊ฒŒ ์ ‘๊ทผ ๋ชปํ•˜๋„๋ก ๋ง‰์œผ๋ฉด, ๋น„๋กœ์†Œ ์ดˆ๊ธฐํ™” ์‹œ๊ธฐ์— ์˜์กด์„ฑ ์ฃผ์ž…์„ ๊ฐ•์ œํ•˜๋ฉด์„œ ํ–‰๋ณตํ•œ ์ƒํ™ฉ์„ ๋งŒ๋“ค ์ˆ˜ ์žˆ๋‹ค.

๋” ๋‚˜์•„๊ฐ€๋ฉด, ์ฝ”๋“œ๋กœ๋งŒ ํ•˜๋ฉด ์ด๋ ‡๊ฒŒ ์ถ”์ƒํ™” ํ•ด์„œ ์‚ฌ์šฉํ•˜๋ฉด ๋” ํŽธํ•˜๋‹ค.

ํ•˜์ง€๋งŒ ์šฐ๋ฆฌ๊ฐ€ ํ•ด์•ผ ํ•˜๋Š” ๊ฒƒ์€ Storyboard๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด์„œ initializer๋กœ injection์„ ํ•˜๊ณ  ์‹ถ์€ ๊ฒƒ์ด๋‹ค! ์–ด๋–ป๊ฒŒ ํ•˜์ง€!

Storyboard?!

๊ธฐ์กด์— ์‚ฌ์šฉํ•˜๋˜ ์นœ๊ตฌ๋Š” ์ด๋Ÿฐ ๋…€์„์ด์—ˆ๋‹ค.

func instantiateInitialViewController() -> UIViewController?

๊ทธ๋ž˜์„œ ์‹ค์ œ ์ฝ”๋“œ๋ฅผ ๋ณด๋ฉด,

guard let controller = UIStoryboard(name: "Folder").instantiateInitialViewController() as? FolderViewController else {
    return
}
 
controller.viewModel = FolderViewModel()

์ด๋Ÿฐ์‹์œผ๋กœ ์ฒ˜๋ฆฌํ–ˆ๋‹ค. Bundle์—์„œ ๋กœ๋“œํ•œ ํ›„์— property & Method ์ฃผ์ž…์ด ์ตœ์„ ์ด์—ˆ๋‹ค! ๊ทธ๋Ÿฐ๋ฐ iOS 13๋ถ€ํ„ฐ ์ƒˆ๋กœ์šด API๊ฐ€ ์ œ๊ณต๋˜์—ˆ๋‹ค.

New APIs!

์—ฌ๊ธฐ์„œ ํ•˜์œ„ 3์ธ๋ฐฉ์ด iOS 13๋ถ€ํ„ฐ ์ƒˆ๋กœ์ƒ๊ธด API์ด๋‹ค! ์—ฌ๊ธฐ์„œ ๊ฐ€์žฅ ๊ด€์‹ฌ์ด ๊ฐ€๋Š” ๋…€์„์€ ์ด๋…€์„์ด๋‹ค.

@MainActor func instantiateViewController<ViewController>(identifier: String, creator: ((NSCoder) -> ViewController?)? = nil) -> ViewController where ViewController : UIViewController

์ € @MainActor๋Š” ๋ญ์ง€..? ์ถ”ํ›„ ์•Œ์•„๋ณด์ž. ์ผ๋‹จ์€ identifier์™€ coder๋ฅผ ๋ฐ›์•„ ViewController๋ฅผ ๋ฆฌํ„ดํ•˜๋Š” ํด๋กœ์ €๋ฅผ ์ œ๊ณตํ•˜๋ฉด ๋‚ด๊ฐ€ ์›ํ•˜๋Š” ViewController๋ฅผ ์ฃผ๋Š” ๋…€์„์ด๋‹ค. identifier๋Š” storyboard์—์„œ ํŠน์ • VC๋ฅผ ํด๋ฆญํ–ˆ์„ ๋•Œ ์ •ํ•  ์ˆ˜ ์žˆ๋Š” ๋ฌธ์ž์—ด์„ ๋งํ•œ๋‹ค.

๋ฐ”๋กœ ์ด๋…€์„์ด๋‹ค. ์™œ ์ด๋…€์„์ด ์žˆ์œผ๋ฉด Storyboard๋ฅผ ์‚ฌ์šฉํ•จ์—๋„ ์˜์กด์„ฑ ์ฃผ์ž…์ด ๊ฐ€๋Šฅํ• ๊นŒ?

Dependency Injection with Storyboard

import UIKit
 
class FolderViewController {
    var viewModel: T
 
    init(viewModel: T) {
        self.viewModel = viewModel
        super.init(nibName: nil, bundle: nil) // code๋กœ VC๋ฅผ ์ƒ์„ฑํ•˜๋Š” ๊ฒฝ์šฐ nib, bundle ๋ชจ๋‘ ๋ถˆํ•„์š”
    }
 
    init?(coder: NSCoder, viewModel: T) {
        self.viewModel = viewModel
        super.init(coder: coder)
    }
 
    @available(*, unavailable, renamed: "init(coder:viewModel:)")
    required init?(coder: NSCoder) {
        fatalError("Invalid way of decoding this ViewController")
    }
}

์ผ๋‹จ ์ด๋ ‡๊ฒŒ coder์™€ viewModel ๋‘˜๋‹ค ๋ฐ›์„ ์ˆ˜ ์žˆ๊ฒŒ ๋งŒ๋“ค์–ด ๋‘์ž. ๊ทธ๋ฆฌ๊ณ  ์‹ค์ œ ์ด๋…€์„์„ instanceํ™” ํ•˜๋Š” ๊ณณ์—์„œ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์‚ฌ์šฉํ•˜์ž.

let storyboard = UIStoryboard(name: "Main", bundle: nil)
let viewModel = storyboard.instantiateInitialViewController { coder -> FolderViewController in
    let viewModel = DefaultRoomListViewModel()
    return .init(coder: coder, viewModel: viewModel) ?? FolderViewController(viewModel: viewModel)
}

์•„์•„ ๋“œ๋””์–ด ์šฐ๋ฆฌ๊ฐ€ ์›ํ•˜๋Š” ๋ชฉํ‘œ๋ฅผ ๋‹ฌ์„ฑํ–ˆ๋‹ค! ์—ฌ๊ธฐ์„œ generic๊นŒ์ง€ ์ถ”๊ฐ€ํ•˜์—ฌ ์‚ฌ์šฉํ•œ๋‹ค๋ฉด, ๋‹ค์Œ๊ณผ ๊ฐ™์ด ๋˜๊ฒ ๋‹ค.

import UIKit
 
class DefaultDIViewController<T>: DefaultViewController {
    var viewModel: T
 
    init(viewModel: T) {
        self.viewModel = viewModel
        super.init(nibName: nil, bundle: nil) // code๋กœ VC๋ฅผ ์ƒ์„ฑํ•˜๋Š” ๊ฒฝ์šฐ nib, bundle ๋ชจ๋‘ ๋ถˆํ•„์š”
    }
 
    init?(coder: NSCoder, viewModel: T) {
        self.viewModel = viewModel
        super.init(coder: coder)
    }
 
    @available(*, unavailable, renamed: "init(coder:viewModel:)")
    required init?(coder: NSCoder) {
        fatalError("Invalid way of decoding this ViewController")
    }
}

Reference