MVI 패턴

준우·2024년 2월 6일
1

SwiftUI 이야기

목록 보기
3/5
post-thumbnail

MVI 패턴은 iOS 보다는 안드로이드에서 많이 사용되는 패턴입니다.

MVI 패턴은 단방향 아키텍처 입니다. 우리가 잘 알고 많이 쓰는 MVC, MVVM 패턴은 양방향 아키텍처입니다. 최근 개발자들 사이에서 양방향 아키텍처에 대한 문제점을 제기하면서, 그 문제점들을 해결할 수도 있는 단방향 아키텍처에 관심이 쏠리고 있습니다.

img

  • View 에서 액션(UI events) 을 입력 받으면,
  • Intent 에서 Model(State) 의 상태 변화를 일으키고
  • 변화된 상태의 모델이 View 에 전달돼서 화면이 바뀌는 것

MVI에서 인텐트가 유저를 관찰하고, 모델이 인텐트를 관찰하고 뷰가 모델을 관찰하고 유저가 뷰를 관찰하는 패턴입니다.

단방향 아키텍처는 무엇인가?

단방향 아키텍처는 한 방향으로만 데이터가 흘러가는 것을 뜻합니다. 앱의 데이터 흐름이 한 방향으로 진행되고, 상태는 모델에서만 관리되는 형태입니다.

  • 데이터와 액션은 한방향으로만 흐른다.
  • View 에 영향을 주는 State 는 한 방향으로만 수정할 수 있다.
  • Model 은 State 를 변화시키고 View 는 State 를 참조만 한다. View 와 State 를 분리

최종적으로, View -> Action(Intent) -> Store(Model) -> View 이렇게 작동합니다. 동기적으로 실행합니다.

UIKit 과 SwiftUI 로도 단방향 아키텍처를 구현할 수 있습니다.

단방향 아키텍처를 구현할 수 있게 해주는 ReactorKit 과 TCA 가 있습니다.

UIKit / SwiftUI 프레임워크 중 어떤 것을 사용하는 지에 따라 두 가지 방식으로 구분이 되는 것 같습니다. :)

  • UIKit + RxSwift + ReactorKit
  • SwiftUI + Combine + TCA

MVI 패턴은 TCA 라이브러리와 같이 단방향 데이터 흐름을 추구하지만, TCA 라이브러리의 의존성을 없앤 것이라고 생각하시면 됩니다.

MVI 패턴은 왜 등장했나?

기존 MVVM 패턴은 2가지 문제를 해결하지 못했습니다.

  • 상태 문제 - State
    • 상태를 관리하기 힘들어지고, 의도하지 않은 방향으로 제어가 되는 문제
  • 부수 효과 - Side Effect
    • 어떤 결과를 얻을지 예상할 수 없으며, 그에 따른 상태 변경의 어려움

MVI 패턴의 장단점은 무엇인가?

장점

  • 유지 관리가 용이하고, 확장 가능한 앱을 만들 수 있음
  • View 생명주기 동안 일관성 있는 상태(State) 를 가짐
  • 모델이 불변하기 때문에, 큰 앱에서 멀티 스레드 안전성이 높음

단점

  • 처음에 패턴을 제대로 이해할 때까지 러닝 커브가 높음
  • RxSwift, Combine 비동기 프레임워크에 대한 지식이 필요함

MVI 패턴을 간단히 구현한 예시

  • State 에시
struct CounterState {
    var count: Int = 0
}
  • Intent 예시
enum CounterIntent {
    case increase
    case decrease
}
  • ViewModel 예시 (기능 로직 분리 역할)
///  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
        }
    }
}
  • View 예시
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()
        }
    }
}

0개의 댓글