View에서 ViewModel을 통해 Model을 전달받는 구조다.
View에서는 오직 UI만 구성하고, UI에 띄울 Data는 ViewModel에서 받아오기만 하면 된다.
View를 분리하긴 했지만, ViewController(VC)에서 View를 사용해야하고 Model도 가져와야 한다. VC가 무거워질 수 밖에 없는 구조이다.
MVVM 패턴은 무거워진 VC를 해결해준다.
ViewModel 폴더가 생겼는데, 오직 뷰컨을 위한 뷰모델을 만들어 주어야 한다.
Model를 관리하는 일은 이제 ViewModel에서 처리하고, ViewController에서는 View만 사용하면 된다.
디렉토리 구조 비교에서 ViewModel의 차이가 있었다.
ViewModel은 어떤 역할을 하고 있을까 ?
이건 아마도 코드를 보면서 이해하는게 더 좋을 것 같다.
ViewModel을 도와주는 Service, Repository 생성하기.
서버에서 받은 Json을 파싱할 구조체를 선언합니다. 참고로 Class로 선언된 이유는 API 연동을 안했고, 내부 저장소인 Realm을 사용하기 때문입니다. 패턴 참고용으로 설명하겠습니다.
class RecordEntity: Object {
@objc dynamic var id: Int = 0
@objc dynamic var type: String = ""
@objc dynamic var tag: String = ""
@objc dynamic var item: String = ""
@objc dynamic var price: Double = 0.0
@objc dynamic var count: Double = 0.0
@objc dynamic var date: Date = Date()
@objc dynamic var memo: String = ""
override static func primaryKey() -> String? {
return "id"
}
}
서버에서 API를 호출하고 Entity로 파싱해서 가져오는 클래스입니다.
class RecordRepository {
let instance = try! Realm()
func postRecordEntity(recordEntity: RecordEntity) {
var id = 0
if let lastRecord = instance.objects(RecordEntity.self).last {
id = lastRecord.id + 1
}
recordEntity.id = id
try? instance.write {
self.instance.add(recordEntity)
}
}
}
Repository로 Entity를 가져왔다면, Model로 다시 파싱해주어야 하는데 RecordService가 그 역할을 합니다.
class RecordService {
let recordRepository = RecordRepository()
func getRecords() -> [Record] {
let recordEntities = recordRepository.getAllRecordEntity()
var records: [Record] = []
for recordEntity in recordEntities {
records.append(parseToRecord(recordEntity: recordEntity))
}
return records
}
func postRecord(record: Record) {
let recordEntity = parseToRecordEntity(record: record)
recordRepository.postRecordEntity(recordEntity: recordEntity)
}
private func parseToRecord(recordEntity: RecordEntity) -> Record {
let record = Record(id: recordEntity.id,
type: recordEntity.type,
tag: recordEntity.tag,
item: recordEntity.item,
price: recordEntity.price,
count: recordEntity.count,
date: recordEntity.date,
memo: recordEntity.memo)
return record
}
}
View모델에서는 Service를 사용해서 Model을 가져오면 끝 입니다.
class CalendarViewModel: ViewModel {
let recordService = RecordService()
var disposeBag = DisposeBag()
var input = Input()
var output = Output()
struct Input {
let nowDate = BehaviorSubject<Date>(value: Date())
let isClickedDatePickerButton = BehaviorSubject(value: false)
let cellData = PublishSubject<(Date, [Record])>()
let indexPath = BehaviorRelay(value: IndexPath())
}
struct Output {
let records = PublishRelay<[Record]>()
let dates = PublishRelay<[Date]>()
let datePickerOpen = PublishRelay<Bool>()
let dateLabel = BehaviorRelay(value: "")
let cellDatas = BehaviorRelay(value: [(Date(), [Record()])])
}
init() {
setBind()
output.records.accept(recordService.getRecords())
output.dates.accept(getDatesOfMonth(date: try! input.nowDate.value()))
}
}