일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
- autolayout
- programmers
- AsyncImage
- format형식
- kakaomap
- Appearance변경
- UserDefaults
- SwiftUI_Preview_Provider
- SWIFT
- alamofire
- 코딩테스트
- snapkit
- NotificationCenter
- ViewModifier
- MapKit
- swiftUI
- CoreLocation
- android
- ios
- Java
- Kotlin
- pod install
- Alert
- UIStackView
- image
- cocoapods
- 백준
- segue
- EventKit
- ios15
- Today
- Total
감자주먹밥
MVVM 개념과 MVVM + Rxswift 본문
Design Pattern ?
개발과정에 공통으로 발생하는 문제를 해결하기 위한게 디자인 패턴이다.
대부분의 디자인 패턴은 복잡하고 거대한 서비스를 만들기 위해 만들어졌다.
MVC, MVP, MVVM
MVVM Pattern ?
비즈니스 로직과 프레젠테이션 로직을 UI로 부터 분리시키기 위해 만들어진 패턴
View - ViewModel - Model로 구성되어 있다.
- View -> UI 입력 출력만 담당
- ViewModel -> View로 부터 오는 입력을 받아 데이터를 처리하고 다시 보내준다.
- Model -> 데이터 처리에 필요한 모델
MVVM 에서 ViewModel에 모든 비지니스 로직이 있어야 하는 것이 아니다. ViewModel에는 화면용 데이터를 갖고 있는것,
Model 은 View용 Model 로 변경하는 정도의 로직 만 있으면 된다.
데이터 의존관계를 일관성있게 유지하기 위해서 VIewModel은 View를 알면 안된다. (의존 관계는 한 방향만!)
한 곳에 동작 및 로직들이 뭉쳐있지 않게 나눠 의존 관계를 한 방향으로 만들어 관리한다.
사용하는 이유
- 뷰모델이 모델과 뷰 사이에 어댑터로서 변경이 생겼을 때 변경을 최소화 하기 위해 사용
- 단위 테스트 하기에 편리하다
RxSwift
- 비동기 및 함수적 스타일 연산자를 사용해 이벤트 기반 코드 작성을 위한 라이브러리
- 러닝커브가 상당히 큼
MVVM에는 RxSwift만?
View와 ViewModel, Model 에서 바인딩을 하기 위해서 사용하는게 가장 큰데, 바인딩은 KVO, NotificationCenter, Delegate를 사용해서도 처리할 수 있다.
RxSwift + MVVM 예제
곰튀김님의 강의와 코드를 보고 비슷하게 따라해 봤습니다.
코드 작성
Rx 공부를 하던 프로젝트에 MVVM을 구현해서 연관없는 코드도 약간 포함되어 있습니다.
프로젝트 구조는 기본적으로 Model, View(VC), ViewModel로 구성되어 있고, 서버에서 받을 데이터의 모델인 DummyModel, 서버와 직접적인 통신을 할 Repository, Repository를 통해 가져온 데이터를 로직에서 사용하는 모델로 변경해주는 Service로 나눠져 있다.
편하게 StoryBoard로 구현을 했다.
ViewController
var viewModel: ViewModel = ViewModel()
override func viewDidLoad() {
super.viewDidLoad()
submitButton.isHidden = true
bind(viewModel)
viewModel.reload()
}
@IBAction func homeButtonTap(_ sender: UIButton) {
viewModel.reload()
}
@IBAction func prevButtonTap(_ sender: UIButton) {
viewModel.loadPrev()
}
@IBAction func nextButtonTap(_ sender: UIButton) {
viewModel.loadNext()
}
func bind(_ viewModel: ViewModel) {
viewModel.dummyText
.bind(to: textLabel.rx.text)
.disposed(by: disposeBag)
}
먼저 UI는 Previous, Home, Next 버튼과 서버로 부터 받은 데이터를 보여줄 Label로 구성되어 있다.
클릭 이벤트가 발생하면 ViewModel한테 데이터 다시 불러와줘~ 라고 만 하고, 데이터를 보여주는 Label도 ViewModel의 데이터에 바인딩만 되어있다.
ViewModel
class ViewModel {
// ViewModel -> View
let dummyText = BehaviorRelay<String>(value: "Waiting...")
...
// View -> ViewModel
...
let service = Service()
let disposeBag = DisposeBag()
init() {...}
func reload() {
service.fetchNow {[weak self] model in
self?.dummyText.accept(model.makeString())
}
}
func loadNext() {
service.fetchNext {[weak self] model in
self?.dummyText.accept(model.makeString())
}
}
func loadPrev() {
service.fetchPrev {[weak self] model in
self?.dummyText.accept(model.makeString())
}
}
}
View에서 이벤트가 들어오면 호출되는 load 함수들을 보면 service에서 서버로 부터 데이터를 받아서 BehaviorRelay에 accept 해준다.
accept하게 되면 VC에서 바인딩 되어 있던 Label의 텍스트가 알아서 변경된다.
Service, Model
class Service {
let repository = Repository()
var currentPage = 1
// Entity -> Model
func fetchNow(onCompleted: @escaping (Model) -> Void) {
currentPage = 1
repository.fetchNow(page: currentPage) { entity in
let model = Model(id: entity.id, title: entity.title, price: entity.price, rating: entity.rating)
onCompleted(model)
}
}
func fetchNext(onCompleted: @escaping (Model) -> Void) {
guard currentPage != 10 else { return }
currentPage += 1
repository.fetchNow(page: currentPage) { entity in
let model = Model(id: entity.id, title: entity.title, price: entity.price, rating: entity.rating)
onCompleted(model)
}
}
func fetchPrev(onCompleted: @escaping (Model) -> Void) {
guard currentPage != 1 else { return }
currentPage -= 1
repository.fetchNow(page: currentPage) { entity in
let model = Model(id: entity.id, title: entity.title, price: entity.price, rating: entity.rating)
onCompleted(model)
}
}
}
struct Model {
let id: Int
let title: String
let price: Int
let rating: Double
func makeString() -> String {
let text = """
ID: \(self.id)
Title: \(self.title)
Price: \(self.price)$
Rating: \(self.rating)
"""
return text
}
}
Repository는 서버에서 JSON 타입을 데이터를 받아오고 Service에서 받은 데이터를 로직에 사용할 Model로 변형 후 다시 내보낸다.
다시 정리하면 View는 ViewModel을 가지고 있으면서 데이터가 변경되는걸 보고 View를 업데이트한다.
ViewModel은 service로 부터 사용할 데이터를 받아 업데이트를 하고 쭉쭉쭉 일관성 있게 의존관계가 이어져 있다.
MVVM의 핵심이 이 의존관계를 일관성 있게 한 방향으로 유지하는 것이다.
결과화면
밑에 텍스트 필드 및 다른 UI는 RxSwift, RxCocoa 써보려고 테스트하던 것.
'Design Pattern' 카테고리의 다른 글
MVVM - UIKit + Combine (0) | 2023.04.19 |
---|---|
MVP 알아보기 (간단한 테스트 작성) (0) | 2023.01.27 |