728x90
MVC 패턴
- Model에서 데이터 구조를 정의한다.
- Controller에서 받아온 데이터를 가공한다.
- View에서 가공한 데이터를 보여준다.
- 예제 코드
import UIKit
class ViewController: UIViewController {
// MARK: - MODEL
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
}
}
// MARK: - CONTROLLER
override func viewDidLoad() {
super.viewDidLoad()
fetchNow()
}
var currentDateTime = Date()
private func updateDateTime() {
let formatter = DateFormatter()
formatter.dateFormat = "yyyy년 MM월 dd일 HH시 mm분"
datetimeLabel.text = formatter.string(from: currentDateTime)
}
private func fetchNow() {
let url = "http://worldclockapi.com/api/json/utc/now"
datetimeLabel.text = "Loading.."
URLSession.shared.dataTask(with: URL(string: url)!) { [weak self] data, _, _ in
guard let data = data else { return }
guard let model = try? JSONDecoder().decode(UtcTimeModel.self, from: data) else { return }
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm'Z'"
guard let now = formatter.date(from: model.currentDateTime) else { return }
self?.currentDateTime = now
DispatchQueue.main.async {
self?.updateDateTime()
}
}.resume()
}
// MARK: - VIEW
@IBOutlet var datetimeLabel: UILabel!
@IBAction func onYesterday() {
guard let yesterday = Calendar.current.date(byAdding: .day,
value: -1,
to: currentDateTime) else {
return
}
currentDateTime = yesterday
updateDateTime()
}
@IBAction func onNow() {
fetchNow()
}
@IBAction func onTomorrow() {
guard let tomorrow = Calendar.current.date(byAdding: .day,
value: +1,
to: currentDateTime) else {
return
}
currentDateTime = tomorrow
updateDateTime()
}
}
MVVM에서 Model 변환 과정
JSON에서
1️⃣ 서버 Model = Entity
-> UtcTimeModel
2️⃣ Model
-> Date(Model)
3️⃣ 화면 Model = ViewModel
-> String(화면)
순서로 변환됩니다.
정리하자면,
Repository
⬇️
Entitiy(Model)
⬇️
Mapper
⬇️
Model
⬇️
Service
⬇️
ViewModel(Model)
⬇️
View
이런식으로 나타낼 수 있습니다.
MVC -> MVVM
위의 MVC 코드를 MVVM로 변경해보겠습니다.
: Entitiy(Model), Repository, Model, Service, ViewModel(Model), View로 분리해줍니다.
Entity | 서버로부터 온 모델 |
Repository | Entity를 서버로부터 가져옴, 즉 서버 모델을 전달해줌 |
Model | Service에서 취급하는 데이터 |
Service | Repository를 사용해서 Entity(서버 모델)를 Model로 변환해줌 (Entity-> Model) |
ViewModel | Service를 사용해서 화면에 보여줘야할 값의 형태(ViewModel)로 변환해줌 (Model -> ViewModel) |
View | ViewModel이 변경되면 화면에 세팅해주는 작업 처리 화면 이벤트에 따라 ViewModel에 값 변경 요청 |
1. 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
}
}
2. 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()
}
}
3. Model
import Foundation
struct Model{
var currentDateTime: Date
}
4. 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 moveDay = Calendar.current.date(byAdding: .day,
value: day,
to: currentModel.currentDateTime) else {
return
}
currentModel.currentDateTime = moveDay
}
}
5. ViewModel
import Foundation
class ViewModel {
var onUpdated: () -> Void = {}
var dateTimeString: String = "Loading.." // 화면에 보여져야할 값 // View를 위한 Model: ViewModel
{
didSet {
onUpdated()
}
}
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 = dateString
}
}
func moveDay(day: Int){
service.moveDay(day: day)
dateTimeString = dateToString(date: service.currentModel.currentDateTime)
}
}
6. View
import UIKit
class ViewController: UIViewController {
@IBOutlet weak var datetimeLabel: UILabel!
@IBAction func onYesterday(_ sender: Any) {
viewModel.moveDay(day: -1)
}
@IBAction func onNow(_ sender: Any) {
datetimeLabel.text = "Loading.."
viewModel.reload()
}
@IBAction func onTomorrow(_ sender: Any) {
viewModel.moveDay(day: 1)
}
let viewModel = ViewModel()
override func viewDidLoad() {
super.viewDidLoad()
viewModel.onUpdated = {[weak self] in
DispatchQueue.main.async {
self?.datetimeLabel?.text = self?.viewModel.dateTimeString
}
}
viewModel.reload()
}
}
MVVM 패턴
- view는 viewModel에 의존해서 화면을 그려낸다.
- viewModel은 서비스에 의존해서 Model을 가져온다.
- Model은 Repository로부터 얻어온 Entity를 가지고 만들어낸다.
[출처]
728x90
'iOS' 카테고리의 다른 글
[iOS] Snapkit 라이브러리로 AutoLayout 잡기 (0) | 2021.12.30 |
---|---|
[iOS] AutoLayout 코드로 그리기 (Code base UI) (0) | 2021.12.30 |
[iOS] FSCalendar 라이브러리 정리 (1) | 2021.10.13 |
[iOS] 텍스트 delegate 정리 (0) | 2021.10.05 |
[RC_week7-8] iOS 아이디어스 클론 코딩 (0) | 2021.08.12 |