iOS 앱 개발에서는 API, Database 및 기타 데이터 소스(IoT 디바이스 데이터)에서 다양한 유형의 데이터를 가져와야 합니다.
점점 더 많은 개발자가 MVVM 아키텍처를 적용하기 시작하면서 데이터 계층이 체계적이지 않은 것 처럼 보입니다.
Codebase가 구축될 때, View Model(이하 VM)은 다양한 데이터 유형에 대한 풍부한 데이터 소스 클라이언트를 유지해야 합니다.
이 글에서, third party 데이터를 올바르게 처리하는 방법에 대해 알아보기 위해 MVVM 아키텍처의 Data Repository Layer(데이터 저장소 계층)에 대해 설명합니다.
앱의 요구사항은 다음과 같습니다.
아래 그래프에서 볼 수 있듯이 다음과 같습니다.
import Foundation
// 1
protocol DataRepositoryProtocol {
func initFetch(complete completion: @escaping (Result<[Meteorite], APIError>) -> Void)
class DataRepository {
var fetchData: (() -> ())?
// 2
private let apiClient: APIClient
// 3
private var dbContainer: Container?
init() {
// 4
self.apiClient = APIClient()
do {
self.dbContainer = try Container()
} catch let error {
Global.printToConsole(message: error.localizedDescription)
}
}
// 5
private func saveToDB(_ meteorites: [APIMeteorite]) {
do {
try dbContainer?.write { transaction in
//TODO: Too much CPU, 13% increased
meteorites.forEach { item in
transaction.add(item, update: .modified)
}
}
} catch (let error) {
Global.printToConsole(message: error.localizedDescription)
}
}
// 5
private func getDbInfo(complete completion: @escaping (Result<[Meteorite], APIError>) -> Void) {
let results = dbContainer?.values(
APIMeteorite.self,
matching:nil
)
completion(.success(results?.filter { $0.geolocation != nil } ?? []))
}
}
extension DataRepository: DataRepositoryProtocol {
// 6
func initFetch(complete completion: @escaping (Result<[Meteorite], APIError>) -> Void) {
apiClient.getListInfo(from: .listRecords) { [weak self] result in
switch result {
case .success(let meteorites):
completion(.success(meteorites))
DispatchQueue.main.async {
self?.saveToDB(meteorites)
}
case .failure(let error):
completion(.failure(error))
DispatchQueue.main.async {
self?.getDBInfo { result in
completion(.success( try! result.get()))
}
}
}
}
}
Data Repository Layer를 추가할 때의 이점은 다음과 같습니다.
import Foundation
final class MeteoriteViewModel {
// 1
private var dataRepo: DataRepositoryProtocol
var meteoriteList = [Meteorite]()
// 2
private var cellViewModels: [MeteoriteListCellViewModel] = [MeteoriteListCellViewModel]() {
didSet {
self.reloadTableViewClosure?()
}
}
// 2
var isLoading: Bool = false {
didSet {
self.updateLoadingStatus?()
}
}
// 2
var alertMessage: String? {
didSet {
self.showAlertClosure?()
}
}
var numberOfCells: Int {
return cellViewModels.count
}
var isSegueAllowed: Bool = false
var selectedMeteorite: Meteorite?
// 2
var reloadTableViewClosure: (()->())?
// 2
var showAlertClosure: (()->())?
// 2
var updateLoadingStatus: (()->())?
let sizeAbsence = Double(APINULL.noSize.rawValue)
// 3
init(dataRepo:DataRepositoryProtocol = DataRepository()) {
self.dataRepo = dataRepo
}
// 4
func initFetch() {
self.isLoading = true
dataRepo.initFetch{ [weak self] result in
self?.isLoading = false
switch result{
case .success(let meteorites):
self?.processMeteoriteToCellModel(meteorites: meteorites)
case .failure(let error):
self?.processError(error: error)
}
}
}
이 설계 패턴을 이해하면, 개발자가 복잡한 세부 정보를 숨기고 균일한 인터페이스를 외부에 노출하기 위해 항상 사용하는 Facade Pattern이라는 것을 알 수 있습니다.
logic에 필요한 Data만 전송하므로 Codeable Model에서 데이터에 액세스 하는 것보다 MVVM과 더 잘 일치합니다.
source : https://github.com/hyengchan/MeteoriteRecordApp
번역: https://blog.devgenius.io/data-repository-layer-in-ios-mvvm-562541b46f91