MVI 패턴은 iOS 보다는 안드로이드에서 많이 사용되는 패턴입니다.
MVI 패턴은 단방향 아키텍처 입니다. 우리가 잘 알고 많이 쓰는 MVC, MVVM 패턴은 양방향 아키텍처입니다. 최근 개발자들 사이에서 양방향 아키텍처에 대한 문제점을 제기하면서, 그 문제점들을 해결할 수도 있는 단방향 아키텍처에 관심이 쏠리고 있습니다.
MVI에서 인텐트가 유저를 관찰하고, 모델이 인텐트를 관찰하고 뷰가 모델을 관찰하고 유저가 뷰를 관찰하는 패턴입니다.
단방향 아키텍처는 한 방향으로만 데이터가 흘러가는 것을 뜻합니다. 앱의 데이터 흐름이 한 방향으로 진행되고, 상태는 모델에서만 관리되는 형태입니다.
최종적으로, View -> Action(Intent) -> Store(Model) -> View 이렇게 작동합니다. 동기적으로 실행합니다.
UIKit 과 SwiftUI 로도 단방향 아키텍처를 구현할 수 있습니다.
단방향 아키텍처를 구현할 수 있게 해주는 ReactorKit 과 TCA 가 있습니다.
UIKit / SwiftUI 프레임워크 중 어떤 것을 사용하는 지에 따라 두 가지 방식으로 구분이 되는 것 같습니다. :)
MVI 패턴은 TCA 라이브러리와 같이 단방향 데이터 흐름을 추구하지만, TCA 라이브러리의 의존성을 없앤 것이라고 생각하시면 됩니다.
기존 MVVM 패턴은 2가지 문제를 해결하지 못했습니다.
struct CounterState {
var count: Int = 0
}
enum CounterIntent {
case increase
case decrease
}
/// ContentView 에서 View 입력을 받으면 데이터를 처리하는 곳
///
/// View 에서 액션(UI events) 을 입력 받으면,
/// Intent 에서 Model(State) 의 상태 변화를 일으키고
/// 변화된 상태의 모델이 View 에 전달돼서 화면이 바뀌는 것
final class CounterViewModel: ObservableObject {
@Published private(set) var state = CounterState()
/// Intent 에서 Model(State) 의 상태 변화를 일으키는 메서드
/// - Parameter intent: CounterIntent
///
/// Ex) intent 가 increase 이면, state 의 count 를 1 증가 시킴
func process(intent: CounterIntent) {
switch intent {
case .increase:
state.count += 1
case .decrease:
state.count -= 1
}
}
}
struct ContentView: View {
@StateObject private var viewModel = CounterViewModel()
var body: some View {
VStack {
Text("Count: \(viewModel.state.count)")
.padding()
HStack {
Button("Increase") {
viewModel.process(intent: .increase)
}
Button("Decrease") {
viewModel.process(intent: .decrease)
}
}
.padding()
}
}
}