반응형

SwiftUI에서 많이 하는 실수 25 : 하나의 상태를 여러 곳에서 동시에 수정해 충돌이 나는 실수

 

SwiftUI는 단방향 데이터 흐름을 기반으로 합니다.
즉, 상태는 한 곳에서만 진짜 값을 가지고, 나머지는 그 값을 “구독”만 해야 합니다.
하지만 같은 모델을 여러 ViewModel/Service가 동시에 수정하면,
어느 쪽 변경이 최종 결과인지 예측하기 어려워지고,
UI와 내부 데이터가 서로 다른 값을 보게 되는 일이 발생합니다.


1. 문제 원인

  • 동일한 배열/딕셔너리를 여러 객체에 참조로 넘겨놓고, 각자 수정
  • 같은 도메인 상태를 여러 ViewModel이 소유
  • 클린 아키텍처/리덕스/TCA의 “single source of truth” 개념을 무시

2. 나타나는 증상

  • 화면 A에서 삭제한 항목이 화면 B에서는 그대로 남아 있음
  • A/B 화면이 번갈아 상태를 바꾸다 보면, 최종 상태가 이상해짐
  • 디버깅 시, 어느 시점에 상태가 변경됐는지 추적이 힘듦

3. 잘못된 코드 예시

final class ListViewModel: ObservableObject {
    @Published var cards: [Card] = []
}

final class DetailViewModel: ObservableObject {
    // ❌ 같은 배열을 참조로 넘겨 받아서 직접 수정
    @Published var cards: [Card]

    init(cards: [Card]) {
        self.cards = cards
    }

    func delete(_ card: Card) {
        cards.removeAll { $0.id == card.id }
    }
}

View 쪽에서:

let listVM = ListViewModel()
let detailVM = DetailViewModel(cards: listVM.cards) // ❌ 복사/참조 모호

4. 올바른 코드 예시

4-1. 상태 소유자 한 곳으로 통합

@MainActor
final class CardsStore: ObservableObject {
    @Published private(set) var cards: [Card] = []

    func delete(_ card: Card) {
        cards.removeAll { $0.id == card.id }
    }
}

4-2. ViewModel은 Store를 참조만

final class ListViewModel: ObservableObject {
    @ObservedObject var store: CardsStore
    init(store: CardsStore) { self.store = store }
}

final class DetailViewModel: ObservableObject {
    @ObservedObject var store: CardsStore
    let cardID: Card.ID

    init(store: CardsStore, cardID: Card.ID) {
        self.store = store
        self.cardID = cardID
    }

    var card: Card? {
        store.cards.first { $0.id == cardID }
    }

    func delete() {
        if let card = card {
            store.delete(card)       // ✅ 단일 소스에서 수정
        }
    }
}
  • 실제 데이터 변경은 CardsStore 한 곳에서만 일어납니다.

5. 정리 및 팁

  • “이 도메인 상태(예: 카드 리스트)의 최종 소유자는 어디인가?”를 먼저 정해야 합니다.
  • 나머지 계층(ViewModel, View, Helper)은 그 소유자를 의존성으로 받아 읽기/요청만하는 구조로 만들면,
    충돌과 불일치가 크게 줄어듭니다.
  • 리덕스/TCA/단일 Store 패턴은 이런 문제를 체계적으로 해결하기 위한 대표적인 접근 방식입니다.

 

반응형
Posted by 까칠코더
,