본문 바로가기
Project/ReceiptMind

💸 iOS MVVM 가계부 앱 - ViewModel(TransactionViewModel) 구조 완벽 정리

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

가계부 앱을 MVVM 구조로 개발하면서 핵심 역할을 하는 TransactionViewModel에 대해 소개합니다.
이 글에서는 ViewModel의 역할, 내부 구조, init 설계 이유, 그리고 View와의 데이터 흐름까지 모두 짚어볼게요.


🎯 ViewModel이란?

MVVM에서 ViewModel은 View와 Model 사이의 중간 매개체로 작동합니다.
사용자의 입력을 받아 Model에 반영하고, Model의 데이터를 가공해 View에 제공합니다.

💡 이 글에서는 실제 프로젝트에 사용한 TransactionViewModel을 기준으로 설명합니다.


📦 전체 구조 살펴보기

final class TransactionViewModel {
    
    // MARK: - Published Properties
    @Published var transaction: ExpenseModel?
    @Published var transactions: [ExpenseModel] = []
    @Published var errorMessage: String?
    
    // MARK: - Derived Data
    var currentCategories: [String] {
        transaction?.transaction.categoryOptions ?? []
    }
    
    var currentCategoryIcons: [String: String] {
        transaction?.transaction.categoryImageMap ?? [:]
    }
    
    // MARK: - Dependencies
    private var cancellables: Set<AnyCancellable> = []
    private let transactionManager = TransactionCoreDataManager.shared
    
    let mode: AddTransactionMode?
    
    // MARK: - Init
    init(mode: AddTransactionMode? = nil) {
        self.mode = mode
        
        if let mode = mode {
            switch mode {
            case .create:
                self.transaction = ExpenseModel(
                    id: UUID(),
                    transaction: .expense,
                    category: "",
                    amount: 0,
                    image: nil,
                    date: Date(),
                    memo: ""
                )
            case .edit(let id):
                readByIDTransaction(by: id)
            }
        } else {
            // 기본 초기화 (예: 홈 화면 등에서 사용)
            self.transaction = nil
        }
    }
}

🔍 핵심 초기화 로직 설명

🧩 mode 기반 설계란?

TransactionViewModel은 생성 시점에 mode라는 enum 값을 받아 create(새로운 항목 생성)인지, edit(기존 항목 수정)인지 구분합니다.

enum AddTransactionMode {
    case create
    case edit(UUID)
}

 

이렇게 설계하면 다음과 같은 장점이 있습니다:

  • 하나의 ViewModel로 두 역할을 처리할 수 있어 코드 재사용성이 높아짐
  • ViewController에서 복잡한 분기를 만들지 않고도 상황에 맞게 대응 가능

🏠 홈 화면 등에서는 왜 transaction = nil?

// 기본 초기화 (예: 홈 화면 등에서 사용)
self.transaction = nil

 

이건 중요한 설계 포인트입니다.

  • 홈 화면 등에서는 단일 항목이 아니라 전체 거래 리스트만 다루기 때문에, 개별 transaction은 필요하지 않습니다.
  • 그래서 ViewModel에서 transaction = nil로 초기화하여 불필요한 상태값을 피하고, 가볍게 유지할 수 있습니다.

🛠️ 주요 기능 함수 

✅ 생성 함수 (createTransaction())

func createTransaction() {
    guard let transaction = transaction else {
        errorMessage = "Transaction no data"
        return
    }
    
    transactionManager.createTransaction(transaction)
        .sink(receiveCompletion: { completion in
            if case .failure(let error) = completion {
                self.errorMessage = error.localizedDescription
            }
        }, receiveValue: { [weak self] savedTransaction in
            self?.transactions.append(savedTransaction)
            self?.transaction = savedTransaction
        })
        .store(in: &cancellables)
}

 

입력값 검증 → Core Data 저장 → 성공 시 목록과 상태 갱신까지 한 번에 처리합니다.


✨ 마무리 요약

  • TransactionViewModel은 생성/수정/조회의 모든 역할을 맡는 ViewModel입니다.
  • mode 값으로 상황을 구분하고, 이에 따라 초기 상태(transaction)를 설정합니다.
  • 홈 화면 등에서는 불필요한 상태 생성을 피하기 위해 transaction = nil로 초기화합니다.
  • 이 구조는 MVVM에서 ViewModel의 책임을 명확히 나누는 좋은 예시입니다.
728x90
LIST