[SwiftUI] CryptoApp: CoreData

Junyoung Park·2022년 11월 3일
0

SwiftUI

목록 보기
84/136
post-thumbnail
post-custom-banner

Save current user's portfolio to Core Data with MVVM | SwiftUI Crypto App #15

CryptoApp: CoreData

구현 목표

  • 코어 데이터를 통해 특정 데이터 핸들링
  • 데이터 CRUD를 통한 UI 인터렉션

구현 태스크

  • 코어 데이터 핸들링 데이터 서비스 클래스 구현
  • 데이터 CRUD 함수 구현
  • 뷰 모델 코어 데이터 서비스 구독
  • 뷰 단의 데이터 업데이트를 통한 UI 인터렉션 구현

핵심 코드

private func fetchPortfolio() {
        let request = NSFetchRequest<PortfolioEntity>(entityName: entityName)
        do {
            savedEntities = try container.viewContext.fetch(request)
        } catch {
            print("Error fetching Portfolio Entities: \(error.localizedDescription)")
        }
    }
  • 컨텍스트로부터 존재하는 모든 엔티티를 패치하는 함수
private func save() {
        do {
            try container.viewContext.save()
        } catch {
            print("Error saving Coredata: \(error.localizedDescription)")
        }
    }
  • 현재 컨테이너의 컨텍스트 정보를 코어 데이터 내에 덮어씌우는 함수
private func applyChange() {
        save()
        fetchPortfolio()
    }
  • 로컬에서 변경된 정보를 컨텍스트에 저장하고, 이를 다시 로드
private func add(coin: CoinModel, amount: Double) {
        let entitiy = PortfolioEntity(context: container.viewContext)
        entitiy.coinID = coin.id
        entitiy.amount = amount
        applyChange()
    }
  • 새로운 엔티티를 추가하는 함수
private func update(entity: PortfolioEntity, amount: Double) {
        entity.amount = amount
        applyChange()
    }
  • 기존의 엔티티 정보를 업데이트하고 새롭게 저장 및 데이터 패치
private func delete(entity: PortfolioEntity) {
        container.viewContext.delete(entity)
        applyChange()
    }
  • 특정 엔티티를 코어 데이터 내부에서 삭제
func updatePortfolio(coin: CoinModel, amount: Double) {
        if let entity = savedEntities.first(where: {$0.coinID == coin.id }) {
            if amount > 0 {
                update(entity: entity, amount: amount)
            } else {
                delete(entity: entity)
            }
        } else {
            if amount > 0 {
                add(coin: coin, amount: amount)
            }
        }
    }
  • 해당 코어 데이터 서비스 클래스 외부에서 접근 가능한 함수
  • 기존 엔티티 정보라면 갱신, 새로운 정보라면 추가
  • amount 값에 따라 해당 코인 데이터의 추가 및 삭제 여부 판단
$allCoins
            .combineLatest(portfolioDataService.$savedEntities)
            .map { (coinModels, portfolioEntities) -> [CoinModel] in
                coinModels
                    .compactMap { coin -> CoinModel? in
                        guard let entity = portfolioEntities.first(where: { $0.coinID == coin.id }) else { return nil }
                        return coin.updateHoldings(amount: entity.amount)
                    }
            }
            .sink { [weak self] coinModels in
                self?.portfolioCoins = coinModels
            }
            .store(in: &cancellables)
  • 홈 뷰 모델에서 코어 데이터 서비스 클래스를 구독하는 부분
  • API를 통해 패치받은 모든 코인 데이터와 코어 데이터에서 패치한 코인 데이터를 함께 비교하기 위한 combineLatest
  • compactMap를 통해 엔티티 정보를 통해 새롭게 코인 업데이트

소스 코드

import Foundation
import CoreData

class PortfolioDataService {
    private let container: NSPersistentContainer
    private let containerName: String = "PortfolioContainer"
    private let entityName: String = "PortfolioEntity"
    @Published var savedEntities: [PortfolioEntity] = []
    init() {
        container = NSPersistentContainer(name: containerName)
        container.loadPersistentStores { [weak self] _, error in
            if let error = error {
                print("Error loading Cor Data: \(error.localizedDescription)")
            }
            self?.fetchPortfolio()
        }
    }
    
    // MARK: PUBLIC
    
    func updatePortfolio(coin: CoinModel, amount: Double) {
        print("Coin: \(coin.id)")
        print("Coin Amount: \(coin.currentHoldings ?? 0.0)")
        print("Coin Name: \(coin.name)")
        if let entity = savedEntities.first(where: {$0.coinID == coin.id }) {
            if amount > 0 {
                update(entity: entity, amount: amount)
            } else {
                delete(entity: entity)
            }
        } else {
            add(coin: coin, amount: amount)
        }
    }
    
    // MARK: PRIVATE
    
    private func fetchPortfolio() {
        let request = NSFetchRequest<PortfolioEntity>(entityName: entityName)
        do {
            savedEntities = try container.viewContext.fetch(request)
        } catch {
            print("Error fetching Portfolio Entities: \(error.localizedDescription)")
        }
    }
    
    private func add(coin: CoinModel, amount: Double) {
        let entitiy = PortfolioEntity(context: container.viewContext)
        entitiy.coinID = coin.id
        entitiy.amount = amount
        applyChange()
    }
    
    private func save() {
        do {
            try container.viewContext.save()
        } catch {
            print("Error saving Coredata: \(error.localizedDescription)")
        }
    }
    
    private func applyChange() {
        save()
        fetchPortfolio()
    }
    
    private func update(entity: PortfolioEntity, amount: Double) {
        entity.amount = amount
        applyChange()
    }
    
    private func delete(entity: PortfolioEntity) {
        container.viewContext.delete(entity)
        applyChange()
    }
}
  • 포트폴리오 코인 엔티티를 다루기 위한 코어 데이터 데이터 서비스 클래스
  • init 단에서 코어 데이터 로드 및 데이터 패치
  • 실제 데이터 CRUD가 일어나는 로직은 private
  • 뷰 모델이 사용하는 데이터 핸들링은 updatePortfolio로 public
  • 뷰 컨텍스트 자체를 새롭게 저장하는 방식
private func saveButtonPressed() {
        guard
            let coin = selectedCoin,
            let amount = Double(quantityText) else { return }
        // save to portfolio
        viewModel.updatePortfolio(coin: coin, amount: amount)
       ...
    }
  • 저장 버튼 클릭 시 현재 입력된 amount 및 코인 정보를 확인
  • 뷰 모델의 updatePortfolio를 통해 포트폴리오 CRUD
private func updateSelectedCoin(coin: CoinModel) {
        if selectedCoin?.id == coin.id {
            selectedCoin = nil
        } else {
            selectedCoin = coin
            if
                let portfolioCoin = viewModel.portfolioCoins.first(where: { $0.id == coin.id }),
                let amount = portfolioCoin.currentHoldings {
                quantityText = "\(amount)"
            } else {
                quantityText = ""
            }
        }
    }
  • 특정 코인 선택 시 포트폴리오 코인인지 확인, amount를 표시

구현 화면

profile
JUST DO IT
post-custom-banner

0개의 댓글