-
[SwiftUI] MVI Design Pattern 이란?Swift/SwiftUI 2022. 12. 26. 15:15
MVVM 을 사용하다 보니 이게 SwiftUI 에서 최선이 아닐 탠데 라는 고민을 하게 되었습니다..
그 고민을 하게 된 원인은 👇👇
1. ViewModel 에서 너무 많은 일을 하고 있다. - Action 와 Property 를 모두 관리하려고 하다 보니 흡사 UIKit 에서 MVC (Massive View Controller) 와 같이 ViewModel 이 Massive 해지는 경험을 하게 되었습니다.
2. Data 의 흐름이 관리가 되지 않는다. - 1번째 이유가 겹치는 이야기인데 ViewModel 에서 Property의 값을 변경하기도 하고 참조하기도 하고 다양한 일을 하다보니 점점 Data Flow 가 어떻게 흐르는지 추적하기가 어려워 졌습니다.
그래서 고민하게 되었고 SwiftUI 에서 각광받고 있는 MVI 패턴에 대해 공부하게 되었습니다.
MVI 패턴은 이미 안드로이드에서 오래전부터 사용하고 있던 디자인 패턴이고 SwiftUI 와 잘 맞는 다고 소문이 나서 몇몇 분들이 열심히 쓰고 계신 디자인 패턴입니다. 그럼 MVI 패턴이 뭔지 알아 보도록 하겠습니다
MVI
Model - 화면에 보여질 Data 를 가지고 있습니다.
View - Model 을 참조하여 Data 를 보여줍니다.
Intent - 유저의 Action에 대한 핸들링, 라이프 사이클에 따른 실행등을 담당합니다.
Container - 그리고 코드 레벨에서 보겠지만 Intent 와 Model 은 컨테이너에 담겨 View 에서 instance 를 들고 있습니다
Container
final class MVIContainer<Intent, Model>: ObservableObject { // 2 let intent: Intent let model: Model private var cancellable: Set<AnyCancellable> = [] init(intent: Intent, model: Model, modelChangePublisher: ObjectWillChangePublisher) { self.intent = intent self.model = model // 3 modelChangePublisher .receive(on: RunLoop.main) .sink(receiveValue: objectWillChange.send) .store(in: &cancellable) } }
View 에서 들고 있을 Container 입니다.
View
extension ListView { static func build() -> some View { let model = ListModel() let intent = ListIntent(model: model) let container = MVIContainer( intent: intent, model: model as ListModelStatePotocol, modelChangePublisher: model.objectWillChange) return ListView(container: container) } }
함수의 리턴값을 통해 View 를 만들어 보여줍니다
Intent 는 Model 에 접근해 값을 바꿔줘야하기 때문에 Model 인스턴스를 들고 있습니다struct ListView: View { // 1 @StateObject private var container: MVIContainer<ListIntent, ListModelStatePotocol> var body: some View { // 2 Text(container.model.text) .padding() .onAppear(perform: { // 3 self.container.intent.viewOnAppear() }) } }
그리고 View 에서는 container 를 StateObject 로 들고 있습니다
ObservedObject 가 아닌 이유는 뷰를 다시 그릴때 마다 container 를 새롭게 init 하지 않기 위함입니다.
Intent
final class ListIntent { // 1 private weak var model: ListModelActionsProtocol? init(model: ListModelActionsProtocol) { self.model = model } func viewOnAppear() { let number = Int.random(in: 0 ..< 100) // 2 model?.parse(number: number) } }
Intent 는 View 에서 호출된 유저의 액션에 따라 Model 에 접근해 값을 변경하고 View 에서 참조할 Data 를 변경합니다.
하지만 View 에서 Intent 의 Model 에 바로 접근할수 없도록 private 으로 선언합니다.
Model
// 1 protocol ListModelStateProtocol { var text: String { get } } // 2 protocol ListModelActionsProtocol: AnyObject { func parse(number: Int) r
1. ListModelStateProtocol 에서는 View 에서 참조할 Property 들에 대한 내용만 들고 있습니다
2. ListModelActionsProtocol 에서 Intent 에서 Model 의 값을 변경할때 호출할 함수들에 대한 내용을 들고 있습니다.// 1 final class ListModel: ObservableObject, ListModelStatePotocol { @Published var text: String = "" } // 2 extension ListModel: ListModelActionsProtocol { func parse(number: Int) { text = "Random number: " + String(number) } }
참고
'Swift > SwiftUI' 카테고리의 다른 글
[SwiftUI] 앱스토어 이미지 애니메이션 구현하기 - namespace, matchedGeometryEffect (0) 2023.01.21 [SwiftUI] .transition(.move(edge: Edge))) - 방향성있는 애니메이션을 구현해보자 (0) 2023.01.13 [SwiftUI] 쌓인 NavigationLink 한번에 없애기 (0) 2022.11.27 [SwiftUI] List 에서 기본 스타일 빼고 SwipeAction 사용하기 (0) 2022.11.17 [SwiftUI] 스켈레톤을 만들어주는 내장 기능 : redacted (0) 2022.11.14