본문 바로가기
Project/ReceiptMind

📚Core Data로 지출 내역 불러오기(Read) 정리

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

✨ 목표

이번 글에서는 가계부 앱에서 Core Data로 저장된 지출 내역을 전체 조회하거나 특정 ID로 조회하는 방법과, Core Data Entity(ExpenseEntity)를 우리가 ViewModel/화면에서 사용하기 위한 데이터 모델(ExpenseModel)로 변환하는 방법을 함께 정리합니다.


✅ 전체 흐름 요약

역할 함수 이름 설명
CoreData → Model 변환 toModel() Entity 데이터를 앱에서 사용할 수 있는 형태로 변환
전체 데이터 읽기 readAllTransaction() 모든 지출 내역을 최신순으로 읽기
특정 데이터 읽기 readTransactionByID(by:) 특정 ID 기반으로 하나의 내역 읽기

 

1️⃣ ExpenseEntity → ExpenseModel 변환: toModel()

extension ExpenseEntity {
    func toModel() -> ExpenseModel? {
        guard
            let idString = self.id,
            let uuid = UUID(uuidString: idString),
            let transactionRaw = self.transaction,
            let transaction = TransactionType(rawValue: transactionRaw),
            let category = self.category,
            let date = self.date,
            let memo = self.memo
        else {
            print("❌ toModel 변환 실패: 필수 값 누락")
            return nil
        }

        let image: UIImage?
        if let path = self.imagePath, !path.isEmpty {
            image = TransactionFileManager.shared.loadImage(from: path)
        } else {
            image = nil
        }

        return ExpenseModel(
            id: uuid,
            transaction: transaction,
            category: category,
            amount: Int(self.amount),
            image: image,
            date: date,
            memo: memo
        )
    }
}

 

🧩 어떤 역할을 하나요?

  • Core Data는 NSManagedObject 기반의 ExpenseEntity 객체를 사용하지만, 앱의 UI나 ViewModel에서는 ExpenseModel이라는 더 가볍고 타입 안정성 있는 모델을 사용하길 원해요.
  • 이 함수는 Entity → Model 변환을 담당합니다.
  • 문자열 기반으로 저장된 UUID, Enum, 이미지 경로 등을 올바른 타입으로 매핑해줘요.

📝 주요 포인트

  • UUID, TransactionType, UIImage 등 타입 안전하게 변환
  • 이미지 경로가 있으면 TransactionFileManager로 이미지 불러오기
  • nil 체크 꼼꼼히 → 실패 시 nil 반환으로 안정성 확보

2️⃣ 전체 지출 데이터 불러오기: readAllTransaction()

func readAllTransaction() -> AnyPublisher<[ExpenseModel], Error> {
    return Future { [weak self] promise in
        guard let self = self else {
            print("❌ TransactionManager: self가 nil이므로 종료")
            return
        }

        let fetchRequest: NSFetchRequest<ExpenseEntity> = ExpenseEntity.fetchRequest()
        fetchRequest.sortDescriptors = [NSSortDescriptor(key: "date", ascending: false)]

        do {
            let expenseEntities = try self.context.fetch(fetchRequest)
            print("✅ Read 성공, 총 \(expenseEntities.count)개 불러옴")

            let transactions = expenseEntities.compactMap { $0.toModel() }
            promise(.success(transactions))

        } catch {
            print("❌ 불러오기 실패: \(error.localizedDescription)")
            promise(.failure(error))
        }
    }
    .eraseToAnyPublisher()
}

 

💡 흐름 설명

  1. fetchRequest를 만들어 모든 ExpenseEntity 데이터를 요청
  2. sortDescriptors로 최신순 정렬
  3. context.fetch()로 Core Data에서 불러오기
  4. compactMap { $0.toModel() }로 ExpenseModel 리스트로 변환
  5. Combine의 Future로 비동기 반환

💎 장점

  • 최신순 정렬로 UI에 바로 사용 가능
  • Entity → Model 변환 덕분에 ViewModel에 바로 전달 가능
  • Combine 구조로 구독해서 UI 자동 업데이트 가능

3️⃣ 특정 지출 ID로 불러오기: readTransactionByID(by:)

func readTransactionByID(by id: UUID) -> AnyPublisher<ExpenseModel?, Error> {
    return Future { [weak self] promise in
        guard let self = self else {
            print("❌ TransactionManager: self가 nil이므로 종료")
            return
        }

        let fetchRequest: NSFetchRequest<ExpenseEntity> = ExpenseEntity.fetchRequest()
        fetchRequest.predicate = NSPredicate(format: "id == %@", id.uuidString)
        fetchRequest.fetchLimit = 1

        do {
            let result = try self.context.fetch(fetchRequest)
            let model = result.first?.toModel()
            if model == nil {
                print("⚠️ 해당 ID로 찾은 데이터 없음")
            } else {
                print("✅ \(id) 읽기 성공")
            }
            promise(.success(model))

        } catch {
            print("❌ ID로 불러오기 실패: \(error.localizedDescription)")
            promise(.failure(error))
        }
    }
    .eraseToAnyPublisher()
}

 

💡 흐름 설명

  1. fetchRequest에 NSPredicate로 필터 조건 설정 (id == UUID)
  2. fetchLimit = 1로 하나만 가져오게 제한
  3. 결과를 toModel()로 변환 후 반환

💎 장점

  • 상세 보기, 수정, 삭제 등 ID 기반 액션에서 사용
  • Core Data의 고유 식별자를 활용하므로 데이터 무결성 보장

🎯 왜 이 방식이 좋은가?

항목 이유
MVVM + Combine 비동기 구조, 구독 구조로 UI 연동에 유리
toModel 사용 Entity와 Model 분리로 유지보수성 향상
확장성 확보 카테고리별, 기간별 검색도 쉽게 확장 가능

 

[함께 보면 좋은 글]

https://explorer89.tistory.com/443

 

💾 Core Data와 FileManager로 지출 내역 저장하기

Core Data + FileManager + Combine 조합으로 데이터와 이미지까지 한 번에 저장하기✨ 목표지출 내역을 저장할 때,기본 정보(날짜, 금액, 메모 등)는 Core Data에선택 이미지가 있다면 FileManager에 저장하고

explorer89.tistory.com

 

728x90
LIST