Design Pattern 을 사용하였을 경우 어떤 이점이 있는가?
- 개발 속도를 향상 시켜줍니다.
- SwiftUI 에서만 사용가능한 것이 아니라 다른 언어들에서도 사용이 가능합니다.
- 코드를 유연하게 해주며 재사용 및 유지보수를 더 간편하게 해줍니다.
그러면 과연 MVVM 은 무엇의 약자인가?
- M: Model
- V: View
- VM : View
MVVM은 M, V, VM 으로 나눠진 약자입니다.
Model 에서 View 로 바로 접근하는 방법은 왜 안좋은 것인가?
이유는 model 클래스는 뷰에 보여주지 않을 많은 로직, 비즈니스 규칙을 포함할 수 없기 때문입니다. 이러한 이유로 ViewModel 를 사용합니다. Model 이 View 에 무언가를 보여주고 싶을 경우 또는 View 가 Model 에 접근을 해야할 경우 ViewModel 를 중간 매체로 사용하게 됩니다. 그리하여 ViewModel은 View에 보여줄 모든 데이터와 정보를 포함하고 있습니다.
즉 Model 에서 View 에 데이터를 전달하고 싶을 경우 ViewModel 에 알림으로서 ViewModel 에 존재하는 데이터를 Bind 하여 View 에 전달합니다. 만일 View가 Model 로부터 데이터를 직접 가져올 때는 Model 에 직접 접근하지 않고 ViewModel 에 Event를 발생시키고 Model 에 접근하게됩니다
MVVM 을 사용해야하는 이유는?
예를 들어 패스워드를 변경하는 화면이 존재한다고 했을 경우 username 과 newPassword, confirmPassword 를 가지고 있는 ViewModel 를 사용하게 되면 화면으로부터 데이터를 가져오거나 화면에 데이터를 공급해줄 수 있게 됩니다. 이것은 username은 username TextField로부터 데이터를 가져오는 것을 의미합니다. 마찬가지로 newPassword, confirmPassword도 각각의 TextField로부터 데이터를 가져올 수 있게 됩니다.
이처럼 ViewModel를 사용할 경우 비밀번호에 대한 validation을 서버 -> 데이터베이스 -> 서버 -> 클라이언트 과정을 거치기 전에 클라이언트 측에서 이미 유효한 비밀번호 (newPassword == confirmPassword) 인지를 미리 검사할 수 있다는 장점을 얻을 수 있습니다.
다음 그림에서는 View -> 서버 -> 클라우드 순으로 데이터에 접근하는 방법입니다. 다음 그림에서 잘못된 부분은 View 가 서버와 직접적으로 통신을 하는 것 입니다. 하지만 위에서 언급했듯이 이것은 안좋은 방법입니다.
좋은 방법은 다음과 같이 View -> View Model -> Webservice/Client -> Cloud 순으로 데이터를 가져오는것 입니다.
그렇다면 Model, View Model, View 은 무엇인가?
Model 이란?
앱 내에서 내가 운용하고자 하는 모든 데이터와 비즈니스 로직이 모델에 있습니다. UI에 독립적이어야 하며, 어떻게 보이는지에 집중하는 View와는 달리, Model은 무엇을 하는지에 집중합니다.
View Model 이란?
View와 Model 을 연결해주는 중간 다리 역할을 합니다. ViewModel 은 Model 에서 변경된 내용을 View 에 알려주어 View가 ViewModel 에서 알려준 변경된 내용을 기반으로 UI를 업데이트 합니다.
View 란?
View 는 Model과 View Model 과 달리 Model 을 단순히 반영해주는 것입니다. Model 과 View Model 에서 데이터와 로직을 모두 구현하여 그것을 가져다 사용하는 것이기 때문에 View는 UI 를 나타내기 위한 코드로만 구성되어야 합니다. 여기서 @State 프로퍼티와 같은 UI 의 변화를 감지하는 프로퍼티 래퍼들은 View 의 변화를 감지하고 View 를 변경시키기 때문에 View 에서 사용되어야 합니다.
MVVM 패턴에서 중요한 점은 View 에 Model 를 노출시켜서는 안된다는 것 입니다
아래 설명들은 다음과 같은 MVVM 패턴을 따르는 간단한 코드를 만드는 과정입니다.
다음은 간단한 Model 예제 코드입니다. Counter 라는 구조체를 만들어 value, isPremium 변수를 만들고 increment() 함수를 만들었습니다. 여기서 mutating 을 사용하는 이유는 구조체의 변수의 값을 구조체 내부에서 변경하게 될 경우 mutating 을 사용해야 하기 때문입니다.
struct Counter {
var value: Int = 0
var isPremium: Bool = false
mutating func increment() {
value += 1
}
}
다음으로는 ViewModel 예제 코드입니다. CounterViewModel 이라는 클래스를 만드는데 Model 과 다르게 클래스로 만든 이유는 ObservableObject 를 상속시키기 위해서 입니다. ObservableObject 를 사용하게 된다면 프로퍼티의 값이 변경된다면 View 에 변경된 내용을 반영하기 위해서 입니다. (struct으로 선언하게 된다면 ObservableObject 를 상속받을 수 없게 됩니다.
import Foundation
import SwiftUI
class CounterViewModel: ObservableObject {
@Published private var counter: Counter = Counter()
var value: Int {
counter.value
}
var premium: Bool {
counter.isPremium
}
func increment() {
counter.increment()
}
}
@Published 프로퍼티 래퍼를 사용하게 되면 counter 인스턴스에서 데이터가 변경된다면 다른 뷰에서도 확인하여 뷰를 업데이트 할 수 있게 됩니다. @ObservedObject 프로퍼티 래퍼를 사용하여 변화된 내용에 대해 다른 뷰에서 대응할 수 있게 됩니다.
struct ContentView: View {
@ObservedObject private var counterVM: CounterViewModel
init() {
counterVM = CounterViewModel()
}
var body: some View {
VStack {
Text("PREMIUM")
.foregroundColor(Color.green)
.frame(width: 200, height: 100)
.font(.largeTitle)
Text("\(counterVM.value)")
.font(.title)
Button("Increment") {
self.counterVM.increment()
}
}
}
}
위에서 Increment 버튼을 눌르게 되면 1씩 증가하는데 만약 3의 배수인 경우에만 PREMIUM 이라고 표현을 한다는 코드를 예제로 만듭니다. 이것은 비즈니스 로직이므로 Model 에서 만들어야합니다.
다음 예제 코드는 Model 에서 비즈니스 로직을 추가하는 코드입니다. 만일 value 가 3의 배수라면 isPremium 을 true 로 만들고 3의 배수가 아니라면 false 로 만듭니다.
struct Counter {
var value: Int = 0
var isPremium: Bool = false
mutating func increment() {
value += 1
// business logic
if value.isMultiple(of: 3) {
// premium
isPremium = true
} else {
// not premium
isPremium = false
}
}
}
위에서 isPremium 이 변경된다면 counterVM 인스턴스의 premium 또한 변경되므로 ContentView 에 변경 사항이 적용되어 Text 가 3일 경우 PREMIUM 으로 변경됩니다.
class CounterViewModel: ObservableObject {
@Published private var counter: Counter = Counter()
var premium: Bool {
counter.isPremium
}
func increment() {
counter.increment()
}
struct ContentView: View {
@ObservedObject private var counterVM: CounterViewModel
init() {
counterVM = CounterViewModel()
}
var body: some View {
VStack {
Text(counterVM.premium ? "PREMIUM" : "")
.foregroundColor(Color.green)
.frame(width: 200, height: 100)
.font(.largeTitle)
Text("\(counterVM.value)")
.font(.title)
Button("Increment") {
self.counterVM.increment()
}
}
}
}
'SwiftUI' 카테고리의 다른 글
Custom Navigation Button (0) | 2023.12.18 |
---|---|
completion, @escaping (0) | 2023.12.15 |
Published (0) | 2023.12.15 |
Chart Proxy (0) | 2023.12.14 |
DownLoading Image from server (AsyncImage) (0) | 2023.12.14 |