-
MVVMArchitecture + Design Pattern 2022. 9. 26. 10:47
Server로 부터 날짜를 가져오고, 날짜를 화면에 띄워주는 서비스를 생각해보자.
먼저 Repository 에서 Server 날짜를 가져온다.
가져오는 Server 의 데이터 (Entity)는 우리가 원하는 데이터 자체가 아니다.
즉, 가공이 필요하다.
따라서 그 다음으로 Service 에서 Repository 에 있는
Entity 를 가져와 이를 우리가 사용할 데이터 형태(Model)로 가공한다.
그 후에는 가공된 데이터를 화면에 보여주어야 하는데,
Service 에는 'Date' Type 이 사용되지만 화면에서는 String Type이 필요하므로 데이터 형태의 변환이 또다시 필요하다.
이때, 그 가공을 맡는 곳을 ViewModel 이라고 한다.
그 후 View 에서 ViewModel 의 데이터를 가져와 화면에 보여주면 일련의 프로세스가 완료된다.
여기서, Model 은 Entity, Model, ViewModel 로 총 3개이다.
각 Model 의 사용 목적은 같으나, 사용처(Repo, Service, VM) 는 다르다.
이 중 비즈니스 로직은 Service 에서 처리하며, 가장 중요한 부분이다.
데이터는 위에서 아래로 이동하며 가공되게 되는 반면,
의존성은 아래에서 위 방향을 향한다.
의존성에 대해 쉽게 말하면, View 는 ViewModel 데이터를 사용하고, ViewModel 은 View 에 대해서 알 수 없는 관계이다. 즉 말그대로 View 가 ViewModel 에 '의존' 한다.
이때, ViewModel 의 값을 View 에 실시간으로 보여주기 위해서는 ViewModel 의 값을 모니터링 해야하는데, 이때 여러 스타일의 Data Binding 이 사용될 수 있다.
asyncronous
asynchronous
그 중 가장 많이 쓰이는 Binding 방식이 RxSwift 를 이용한 방식이다. RxSwift 가 Data Binding 에 유리한 몇가지 특징 (asynchronous process in a stream way 등) 을 가지고 있으나, 반드시 Binding 에 이것만 쓰여야 하는 것은 아니다.
위와 같은 흐름이 자연스러운 흐름이라면 MVP 에서는 VM 이 View 에게 명령하여 방향이 반대로 흐르게 되고,
MVC 에서는 Service 가 View, Repository 에게 모두 명령해서 또한 데이터 흐름이 일관되지 못하게 된다.
따라서 두 (MVP, MVC) 는 데이터 Flow 가 부자연스럽다고 할 수 있다.예시 코드
Entity
import Foundation struct UtcTimeModel: Codable { let id: String let currentDateTime: String let utcOffset: String let isDayLightSavingsTime: Bool let dayOfTheWeek: String let timeZoneName: String let currentFileTime: Int let ordinalDate: String let serviceResponse: String? enum CodingKeys: String, CodingKey { case id = "$id" case currentDateTime case utcOffset case isDayLightSavingsTime case dayOfTheWeek case timeZoneName case currentFileTime case ordinalDate case serviceResponse } }
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 } }
Model
import Foundation struct Model { var currentDateTime: Date }
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)) } }
View ( ViewController)
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() } }
출처: https://www.youtube.com/watch?v=M58LqynqQHc
'Architecture + Design Pattern' 카테고리의 다른 글
ReactorKit (0) 2022.10.12 Clean Architecture + MVVM (0) 2022.10.03 Clean Architecture (0) 2022.09.25 Delegate & Protocol (0) 2022.03.28 Architecture Patterns ( MVC, MVP,MVVM ) ( with iOS, Swift) (0) 2021.10.21