꾸준히 안타치기
MVVM + Rxswift + UIkit을 사용하며 알게된 것 본문
MVVM의 장점 / ⭐️⭐️⭐️View와 그외 요소들간의 의존성 분리
모듈화가 가능 -> 모듈화가 잘되면 유닛테스트에 용이
뷰와 모델의 간의 종속성이 줄어들어 소스코드를 보기가 편하다.
뷰를 직접 조작하지 않아 영역이 독립적이다.
모듈화가 되어있기 때문에 문제있는 부분만 고치면되서 유지보수가 용이하다.
MVC = 무거워지고 유지보수가 어려워서 개선되서 나온것이 MVVM 선언형 패턴
M 모델 구조체 - 앱의데이터와 비지니스 로직 캡슐화
V는 사용해오던 뷰컨트롤러, UI
ViewModel- 중개자
뷰모델이 model을 갖고 있고, 데이터가 바뀌면 알수있다. 앱의 로직담당
모델이 데이터가 변경되면, 뷰모델에 알려준다.
뷰가 변경되면 -> 액션을 취하면 뷰모델에게 알리고 -> 뷰를 갱신함( 뷰는 뷰모델을 구독하고 있고, 바인딩 되어있음 )
뷰는 모델에 접근하는 대신 뷰모델의 속성에 바인딩한다.
뷰모델은 뷰의 상태를 저장하고, 뷰에서 발생하는 액션에 따라 수행할 앱의 기능을 정의하는 명령을 구현한다.
ex) 모델 데이터 업데이트, 뷰모델 값 변경
ViewController가 뷰모델을 가지고있고, view와 바인딩
import UIKit
import RxSwift
import RxCocoa
class ViewController: UIViewController {
@IBOutlet var datetimeLabel: UILabel!
@IBAction func onYesterday() {
viewModel.moveDay(day: -1)
}
@IBAction func onNow() {
datetimeLabel.text = "Loading.."
viewModel.reload()
}
@IBAction func onTomorrow() {
viewModel.moveDay(day: 1)
}
let viewModel = ViewModel()
let disposeBag = DisposeBag()
override func viewDidLoad() {
super.viewDidLoad()
viewModel.dateTimeString
.bind(to: datetimeLabel.rx.text)
.disposed(by: disposeBag)
viewModel.reload()
}
}
Viewmodel - 로직 기능 구현
import Foundation
import RxRelay
class ViewModel {
let dateTimeString = BehaviorRelay(value: "Loading..")
let service = Service()
private func dateToString(date: Date) -> String {
let formatter = DateFormatter()
formatter.dateFormat = "yyyy년 MM월 dd일 HH시 mm분"
return formatter.string(from: date)
}
func reload() {
// Model -> ViewModel
service.fetchNow { [weak self] model in
guard let self = self else { return }
let dateString = self.dateToString(date: model.currentDateTime)
self.dateTimeString.accept(dateString)
}
}
func moveDay(day: Int) {
service.moveDay(day: day)
dateTimeString.accept(dateToString(date: service.currentModel.currentDateTime))
}
}
model - 앱의 데이터
import Foundation
struct Model {
var currentDateTime: Date
}
Repository
import Foundation
class Repository {
func fetchNow(onCompleted: @escaping (UtcTimeModel) -> Void) {
let url = "http://worldclockapi.com/api/json/utc/now"
URLSession.shared.dataTask(with: URL(string: url)!) { data, _, _ in
guard let data = data else { return }
guard let model = try? JSONDecoder().decode(UtcTimeModel.self, from: data) else { return }
onCompleted(model)
}.resume()
}
}
Service
import Foundation
class Service {
let repository = Repository()
var currentModel = Model(currentDateTime: Date()) // state
func fetchNow(onCompleted: @escaping (Model) -> Void) {
// Entity -> Model
repository.fetchNow { [weak self] entity in
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm'Z'"
guard let now = formatter.date(from: entity.currentDateTime) else { return }
let model = Model(currentDateTime: now)
self?.currentModel = model
onCompleted(model)
}
}
func moveDay(day: Int) {
guard let movedDay = Calendar.current.date(byAdding: .day,
value: day,
to: currentModel.currentDateTime) else {
return
}
currentModel.currentDateTime = movedDay
}
}
https://github.com/iamchiwon/mvvm_final
- MVVM 이 DataBinding 자체를 말하는 것이 아니다.
- MVVM 에서 VM에 모든 비지니스 로직이 있어야 하는 것이 아니다.
- 비지니스 로직은 Service 같은 곳에 있어야 하고
- VM에서는 화면용 데이터를 갖고 있는것,
- Model 을 View용 Model 로 변경하는 정도의 로직 만 있으면 된다.
- MVVM의 데이터 의존관계를 일관성있게 유지하기 위해서 VM은 V를 알면 안된다.
- MVVM 의 DataBinding를 위해서 반드시 RxSwift 같은 것을 사용해야만 하는 것은 아니다.
🧑💻 생각정리
MVVM을 에서 뷰를 바인딩할때 꼭 Rxswift를 사용할 필요는없다.
MVVM구조를 알고, Rxswift를 별도로 공부한 다음에 적용하는 것이 좋을듯, Rxswift양이 많음..
UIkit에서 MVVM을 사용하기는 조금 빡쎄다.
SwiftUI 를 하다보면 구성 자체가 MVVM으로 되어 있어 어쩔 수 없이 MVVM을 사용하게 된다.
(SwiftUI는 C가 없고 Model과 View만 있는 형태. swiftUI에서 뷰는 모델과 직접 바인딩 할 수 있다.)
Rx는 테이블뷰를 델리게이트패턴을 사용하지 않고, 옵져버블과 바인딩해서 사용한다.
참고링크
https://tech.olx.com/clean-architecture-and-mvvm-on-ios-c9d167d9f5b3
'iOS > 디자인패턴' 카테고리의 다른 글
MVVM의 개념과 나오게된 배경 (0) | 2022.10.05 |
---|---|
싱글턴 패턴 (0) | 2022.05.25 |
MVC 패턴(Model - View -Controller) (0) | 2021.12.16 |
프로그래밍 디자인패턴 (0) | 2021.12.16 |