안녕하세요.
어제에 이어서 오늘의 TIL 시작합니다~
오늘은 의존성과 관련된 개념들을 이해하는데 시간이 한참 걸렸네요.
오늘 학습한 내용은
1. MVVM 에서 화면 이동 시 데이터 전달
2. 의존성 / 주입 / 의존성 주입
입니다.
1. MVVM 에서 화면 이동 시 데이터 전달
1-1 MVC 의 경우
일반적으로 MVC패턴에서는 ViewController가 Model을 가지고 있었습니다.
그리고 다른 화면으로 이동하는 것도 ViewController에서 처리했습니다.
그래서 화면을 이동할 때, ViewController에서 이동할 화면의 ViewController에 직접적으로 데이터를 전달했었습니다.
let nextVC = nextViewController()
// 여기서 데이터 전달
nextVC.data = data(뷰 컨트롤러에서 가지고 있는 데이터)
nextVC.modalPresentationStyle = .fullScreen
present(nextVC, animated: true, completion: nil)
1-2 MVVM 패턴의 경우(1)
하지만 MVVM패턴에서 데이터는 ViewModel이 가지고 있습니다.
그래서 MVC패턴과는 달리 MVVM패턴의 ViewController(=View)에서 데이터를 직접 전달해주지 못합니다.
그리고 전달할 대상도 ViewController가 아닌 ViewModel입니다.
따라서 ViewController에서는 ViewModel이 가진 데이터를 이동할 화면의 ViewModel로 전달해야 합니다.
let nextVC = nextViewController()
// 다음 화면의 뷰 모델
let nextVM = nextViewModel()
// 다음 화면의 뷰모델이 가져야 하는 데이터
nextVM.data = viewModel.data(뷰 모델에서 가지고 있는 데이터)
// 데이터 전달
nextVC.viewModel = detailVM
nextVC.modalPresentationStyle = .fullScreen
present(nextVC, animated: true, completion: nil)
1-3 MVVM 패턴의 경우(2)
다른 방법으로 ViewModel에서 데이터를 리턴하는 함수를 미리 만들어둘 수 있습니다.
func getNextViewModel() -> NextViewModel {
// 데이터를 전달할 뷰 모델
let nextVM = nextViewModel()
// 데이터 전달
nextVM.data = self.data
// 리턴
return nextVM
}
(뷰에서)
let nextVM = viewModel.getNextViewModel()
nextVC.viewModel = detailVM
이렇게 한다면 데이터를 전달하는 로직을 뷰 모델에서 처리할 수 있다는 장점이 있습니다.
2. 의존성 / 주입 / 의존성 주입
MVVM 패턴은 View와 Model의 의존성을 분리시킨다는 특징이 있다고 합니다.
의존성(Dependency)이란?
한 객체가 다른 객체를 의존하는 것을 의존성이라고 합니다.
조금 더 풀어서 이야기하면 의존하는 객체가 수정되면 다른 객체가 영향을 받는 관계입니다.
아래의 예시에서 Cafe는 Sell에 의존하고 있습니다.
(Cafe 내부에서 Sell을 사용하고 있습니다.)
class Sell {
func sellCoffee() {
print("에스프레소를 팔았습니다.")
}
func sellDesset() {
print("케이크를 팔았습니다.")
}
}
struct Cafe {
var sell: Sell
func sellCoffee() {
sell.sellCoffee()
}
func sellDesset() {
sell.sellDesset()
}
}
의존성이 있으면 다른 객체가 영향을 받는다는 점에서 재활용성을 떨어트립니다.
예시로 Sell이 SellB로 수정되면 Cafe도 수정해야 합니다.
class SellB {
func sellCoffee() {
print("아메리카노를 팔았습니다.")
}
func sellDesset() {
print("쿠키를 팔았습니다.")
}
}
struct Cafe {
// Sell -> SellB로 수정
var sell: SellB
func sellCoffee() {
sell.sellCoffee()
}
func sellDesset() {
sell.sellDesset()
}
}
재활용성이 떨어지면 유지/보수가 어려워지고 생산성이 떨어질 수 있습니다.
그래서 의존성을 해결하려고 나온 방법이 의존성 주입입니다.
주입(Injection)이란?
의존성 주입에 대한 설명에 앞서 주입을 설명하겠습니다.
주입은 외부에서 객체를 생성해서 넣는 것입니다.
class Sell {
var coffee: String
var dessert: String
init(coffee: String, dessert: String) {
self.coffee = coffee
self.dessert = dessert
}
func sellCoffee() {
print("\\(coffee)를 팔았습니다.")
}
func sellDesset() {
print("\\(coffee)를 팔았습니다.")
}
}
struct Cafe {
var sell: Sell
func sellCoffee() {
sell.sellCoffee()
}
func sellDesset() {
sell.sellDesset()
}
}
let menuA = Sell(coffee: "에스프레소", dessert: "케이크")
var aCafe = Cafe(sell: menuA)
코드를 보시면 외부에서 menuA라는 객체를 생성해서 aCafe에 주입했습니다.
Sell은 외부에서 객체를 주입 받기 위해 생성자가 생겼습니다.
하지만 단순히 주입 == 의존성 주입은 아닙니다.
의존성 주입(Dependency Injection)이란?
이제 의존성 주입에 대해 설명하겠습니다.
의존성 주입이란 의존하는 객체를 외부에서 생성한 후 전달받아 사용하는 것입니다.
그리고 의존 관계 역전 법칙(Dependency Inversion Principle)을 지킵니다.
- 상위 모듈은 하위 모듈에 의존해서는 안된다. 상위 모듈과 하위 모듈 모두 추상화에 의존해야 한다.
- 추상화는 세부 사항에 의존해서는 안된다. 세부사항이 추상화에 의존해야 한다.
스위프트에서 추상화된 객체는 Protocol이 있다고 합니다.
아래의 코드를 예시로 설명하겠습니다.
class Sell: Menu {
var coffee: String
var dessert: String
init(coffee: String, dessert: String) {
self.coffee = coffee
self.dessert = dessert
}
func sellCoffee() {
print("\\(coffee)를 팔았습니다.")
}
func sellDesset() {
print("\\(dessert)를 팔았습니다.")
}
}
protocol Menu {
func sellCoffee()
func sellDesset()
}
struct Cafe {
// 이제 Menu를 의존합니다!!
var sell: Menu
func sellCoffee() {
sell.sellCoffee()
}
func sellDesset() {
sell.sellDesset()
}
mutating func changeMenu(menu: Menu) {
self.sell = menu
}
}
let menuA = Sell(coffee: "에스프레소", dessert: "케이크")
var aCafe = Cafe(sell: menuA)
let menuB = Sell(coffee: "아메리카노", dessert: "쿠키")
aCafe.changeMenu(menu: menuB)
앞선 코드와 차이점 중 중요하게 보셔야 할 부분은
1. 프로토콜의 추가 (protocol Menu)
2. 의존관계의 변화 (Sell -> Menu), (Cafe -> Menu)
(코드를 보시면 Cafe에서 sell의 타입으로 이제 Sell이 아닌 Menu를 사용합니다.)
입니다.
사진에 볼 수 있듯이 중간에 Menu가 생기면서 이제 Cafe와 Sell은 의존관계가 아닙니다.
Cafe와 Sell이 독립적으로 작동할 수 있게 되었습니다.
덕분에 각 객체를 수정할 때 다른 객체 내부에서 또 수정해야 하는 일이 없어졌습니다!
(외부에서 menuB를 생성하고 aCafe에 주입할 수 있기 때문에 내부 코드를 건드릴 필요가 없어졌죠.)
마지막으로 의존성 주입의 장점은 다음과 같다고 합니다.
- Unit Test가 용이해진다.
- 코드의 재활용성을 높여준다.
- 객체 간의 의존성(종속성)을 줄이거나 없엘 수 있다.
- 객체 간의 결합도이 낮추면서 유연한 코드를 작성할 수 있다.
참고:
https://80000coding.oopy.io/68ee8d89-5d05-449d-87e2-5fba84d604ca
https://ios-daniel-yang.tistory.com/71
TIL 이렇게 쓰는게 맞을까요?...
가볍게 쓰려고 선택한 TIL인데 생각보다 시간을 많이 소모하네요.
아니면 다음엔 더 간략하게 적을까 싶기도 합니다.