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