중첩된 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)
]
}
}
- 구조:
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 설계를 지향해야 한다.
PREVIOUSAggregate Model (1)