Aggregate Model (2)

 

중첩된 Observable 객체의 한계와 Observation 프레임워크의 해결

과거 ObservableObject 기반의 중첩 구조에서 발생하던 UI 새로고침 버그를 분석하고, 최신 @Observable 매크로가 이를 어떻게 개선하는지 정리한다.

1. 중첩된 Observable 구조 분석 (과거 방식)

강좌에서는 예시로 ExpenseTracker(최상위 스토어) 내부에 Expenses(하위 스토어)라는 또 다른 ObservableObject가 포함된 구조를 보여준다.

class ExpenseTracker: ObservableObject {
    @Published var name: String
    @Published var expenses: Expenses
    
    init() {
        name = "My name"
        expenses = Expenses()
    }

}

class Expenses: ObservableObject {
    
    @Published var name: String
    @Published var items: [Expense]

    init() {
        name = "John Smith"
        items = [
            Expense(name: "Lunch", type: "Business", cost: 25.47, isDeletable: true),
            Expense(name: "Taxi", type: "Business", cost: 17.0, isDeletable: true),
            Expense(name: "Sports Tickets", type: "Personal", cost: 75.0, isDeletable: false)
        ]
    }   
}

Image

  • 구조: ExpenseTracker(부모) -> Expenses(자식) -> items(데이터 배열).
  • 문제 상황: 부모 객체를 통해 자식 객체 내부의 배열(items)에 데이터를 추가(append)하면, 데이터 자체는 정상적으로 늘어나지만 UI가 자동으로 갱신되지 않는다.
  • 원인: ObservableObject는 내부의 속성이 바뀔 때만 알림을 보낼 뿐, 중첩된 객체 깊숙한 곳에서 발생한 변화(버블링)를 상위로 전달하지 못하기 때문이다.

2. Observation 프레임워크를 이용한 문제 해결

iOS 17에서 도입된 Observation 프레임워크의 @Observable 매크로를 사용하면 이 문제가 직관적으로 해결된다.

[코드 1] @Observable 매크로를 적용한 클래스 리팩토링

  • 로직 요약:
    • 부모 클래스와 자식 클래스 모두에 @Observable 매크로를 적용한다.
    • 기존의 @Published 키워드와 ObservableObject 프로토콜 채택을 삭제한다.
    • View에서 StateObject 대신 일반 @State를 사용하여 인스턴스를 관리한다.

3. 리팩토링 결과 및 특징

  • UI 즉시 반영: @Observable은 속성 추적(Property Tracking) 방식이 정밀하여, 중첩된 객체 내부의 데이터 변화도 SwiftUI가 정확히 감지하고 UI를 다시 그린다.
  • 코드 간소화: @Published를 일일이 선언할 필요가 없어 코드가 담백해진다.

4. 근본적인 설계 고민 (Next Step)

Observation 프레임워크가 기술적인 문제를 해결해주긴 하지만, “과연 중첩된 Observable 구조가 최선인가?”라는 의문이 남는다.

  • 한계: 기술적으로는 작동하더라도, 객체 간의 결합도가 높아지고 구조가 복잡해지는 단점은 여전하다.
  • 예고: 다음 단계에서는 중첩된 구조를 아예 제거하고 더 깔끔하게 데이터를 관리하는 방안을 모색한다.

💡 핵심 요약

ObservableObject 시절의 중첩 구조는 UI가 갱신되지 않는 치명적인 단점이 있었으나, iOS 17의 @Observable은 이를 기술적으로 해결한다. 하지만 더 나은 아키텍처를 위해서는 중첩 구조 자체를 탈피하고 도메인별로 평탄화(Flattening)된 Store 설계를 지향해야 한다.