본문 바로가기
Project/ReceiptMind

🧾 iOS MVVM 가계부 앱 - 거래 내역 조회(Read), 수정(Update), 삭제(Delete) 기능 완벽 구현

by 밤새는 탐험가89 2025. 8. 3.
728x90
SMALL

이전 글에서는 MVVM 구조에서의 TransactionViewModel 전반 구조와 초기화 방식에 대해 정리했습니다.
이번 글에서는 그 ViewModel 내부에서 수행되는 데이터 읽기, 수정, 삭제 기능을 소개합니다.


✳️ 전체 흐름 요약

모든 데이터 처리는 TransactionCoreDataManager를 통해 Core Data와 통신하며, Combine을 통해 비동기 스트림을 처리합니다.

 

📌 관련 구조:

  • TransactionViewModel → View에서 호출
  • TransactionCoreDataManager → Core Data와 실제 상호작용
  • @Published → UI 바인딩을 위한 데이터 상태 반영

📖 Read - 거래 내역 불러오기

✅ 모든 거래 내역 불러오기 (readAllTransactions())

func readAllTransactions() {
    transactionManager.readAllTransaction()
        .sink(receiveCompletion: { completion in
            switch completion {
            case .finished:
                break
            case .failure(let error):
                self.errorMessage = error.localizedDescription
            }
        }, receiveValue: { [weak self] savedTransaction in
            self?.transactions = savedTransaction
        })
        .store(in: &cancellables)
}
  • Core Data에서 전체 거래 내역을 불러와 transactions 배열에 저장합니다.
  • 실패 시 에러 메시지를 바인딩하여 View에 표시할 수 있습니다.

💡 홈 화면, 월별 통계 화면 등에서 사용됩니다.


🔍 특정 거래 불러오기 (readByIDTransaction())

func readByIDTransaction(by id: UUID) {
    transactionManager.readTransactionByID(by: id)
        .receive(on: DispatchQueue.main)
        .sink(receiveCompletion: { [weak self] completion in
            if case .failure(let error) = completion {
                self?.errorMessage = error.localizedDescription
            }
        }, receiveValue: { [weak self] readedTransaction in
            self?.transaction = readedTransaction
        })
        .store(in: &cancellables)
}

 

  • 특정 UUID에 해당하는 거래 내역 하나만 읽어옵니다.
  • 편집 모드 진입 시 기존 정보를 불러와 form에 채워넣을 때 사용됩니다.

 

🛠️ Update - 거래 내역 수정하기

func updateTransaction() {
    guard let transaction = transaction else {
        errorMessage = "Transaction no data"
        return
    }
    
    transactionManager.updateTransaction(transaction)
        .receive(on: DispatchQueue.main)
        .sink(receiveCompletion: { [weak self] completion in
            if case .failure(let error) = completion {
                self?.errorMessage = error.localizedDescription
            }
        }, receiveValue: { [weak self] updated in
            self?.transaction = updated
        })
        .store(in: &cancellables)
}
  • 사용자가 변경한 값을 transaction에 반영한 뒤 저장합니다.
  • 성공 시 최신 상태로 ViewModel의 값을 갱신합니다.
  • Combine을 통해 UI는 자동으로 반영됩니다.

🧠 이점: View와 Model 간 동기화 유지 + 재사용 가능성 ↑


🗑️ Delete - 거래 내역 삭제하기

func deleteTransaction(by id: UUID) {
    transactionManager.deleteTransaction(id: id)
        .receive(on: DispatchQueue.main)
        .sink(receiveCompletion: { [weak self] completion in
            if case .failure(let error) = completion {
                self?.errorMessage = "삭제 실패: \(error.localizedDescription)"
            }
        }, receiveValue: { [weak self] success in
            guard let self = self else { return }
            if success {
                print("✅ ViewModel: CoreData 삭제 성공")
                
                // 배열에서 삭제
                self.transactions.removeAll { $0.id == id }
                
                // 현재 선택된 트랜잭션도 초기화
                if self.transaction?.id == id {
                    self.transaction = nil
                }
            } else {
                self.errorMessage = "삭제 실패: 알 수 없는 이유"
            }
        })
        .store(in: &cancellables)
}

 

  • 해당 거래 내역을 Core Data에서 삭제하고, ViewModel에서도 상태를 정리합니다.
  • 삭제 성공 시:
    • 목록에서 제거
    • 현재 선택된 거래(transaction)도 초기화
  • 삭제 실패 시 에러 메시지 노출

🔁 상태 흐름 다이어그램 (간단 정리)

[View Action]
    ↓
[TransactionViewModel 함수 호출]
    ↓
[TransactionCoreDataManager 통해 Core Data 처리]
    ↓
[Combine 통해 결과 수신 → @Published 업데이트]
    ↓
[UI 자동 반영]

 


💡 ViewModel이 Combine + Core Data를 감싸는 이유?

  • Combine을 직접 View에서 쓰지 않고, ViewModel에서 처리하면 코드가 응집도 높고 테스트 용이
  • Core Data의 복잡한 처리 로직을 View에서 분리할 수 있어 MVVM의 역할 분리가 명확해짐
  • 상태 변화는 모두 @Published로 반영되어 SwiftUI 또는 UIKit 모두에서 효율적으로 UI 갱신 가능
728x90
LIST