상태 프로퍼티는 뷰의 상태를 저장하는 방법을 제공하며 해당 뷰에만 사용할 수 있다. 즉, 하위 뷰가 아니거나 State 바인딩이 구현되어 있지 않은 뷰는 접근할 수 없다.
상태 프로퍼티는 일시적인 것이어서 부모 뷰가 사라지면 그 상태도 사라진다. 반면 Observable 객체는 여러 다른 뷰들이 외부에서 접근할 수 있는 지속적인 데이터를 표현하기 위해 사용된다.
Observable 객체는 ObservableObject 프로토콜을 따르는 클래스나 구조체 형태를 취한다. Observable 객체는 데이터의 특성과 출처에 따라 앱마다 다르겠지만, 일반적으로는 시간에 따라 변경되는 하나 이상의 데이터 값을 모으고 관리하는 역할을 담당한다. 또한, Observable 객체는 타이머나 알림과 같은 이벤트를 처리하기 위해 사용될 수도 있다.
Observable 객체는 게시된 프로퍼티로서 데이터 값을 게시한다. 그런 다음, Observer 객체는 게시자를 구독하여 게시된 프로퍼티가 변경될 때마다 업데이트를 받는다. 앞에서 설명한 상태 프로퍼티처럼, 게시된 프로퍼티와의 바인딩을 통해 Observable 객체에 저장된 데이터가 변경됨을 반영하기 위해 SwiftUI 뷰는 자동으로 업데이트될 것이다.
- Combine 프레임워크에 포함되어 이는 Observable 객체는 게시자와 구독자 간의 관계를 쉽게 구축할 수 있도록 도입되었다.
- Combine 프레임워크는 여러 게시자를 하나의 스트림으로 병합하는 것부터 게시된 데이터를 구독자 요구에 맞게 변형하는 것까지 다양한 작업을 수행하는 커스텀 게시자 구축 플랫폼을 제공한다.
- 하지만, 일반적으로는 내장된 게시자 타입들 중 하나면 충분할 것이다.
실제로 Observable 객체의 게시된 프로퍼티를 구현하는 가장 쉬운 방법은 프로퍼티를 선언할 때 @Published 프로퍼티 래퍼를 사용하는 것이다. 이 래퍼는 래퍼 프로퍼티 값이 변경될 때마다 모든 구독자에게 업데이트를 알리게 된다.
다음의 구조체 선언은 두 개의 게시된 프로퍼티를 가진 간단한 Observable 객체 선언을 보여준다.
import Foundation import Combine class DemoData: ObservableObject { @Published var userCount = 0 @Published var currentUser = "" init() { // 데이터 초기화 코드 updateData() } func updateData() { // 데이터를 최신 상태로 유지하기 위한 코드 } } - 구독자는 Observable 객체를 구독하기 위해 @ObservableObject 또는 @StateObject 프로퍼티 래퍼를 사용한다.
- 구독하게 되면 그 뷰 및 모든 하위 뷰가 상태 프로퍼티에서 사용했던 것과 같은 방식으로 게시된 프로퍼티에 접근하게 된다.
- 앞의 DemoData 클래스의 인스턴스를 구독하도록 설계된 간단한 SwiftUI 뷰는 다음과 같다.
import SwiftUI struct ContentView: View { @ObservedObject var demoData : DemoData = DemoData() // demoData는 DemoData 구조체 타입이고, DemoData야. var body: some View { Text("\(demoData.currentUser), you are user number \(demoData.userCount)") } } struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView(demoData: DemoData()) } } - 게시된 데이터가 변경되면 SwiftUI는 새로운 상태를 반영하도록 자동으로 뷰 레이아웃 렌더링을 다시 할 것이다.
State 객체
상태 객체(State object) 프로퍼티 래퍼 @StateObject는 @ObservedObject 래퍼의 대안이다. 상태 객체와 관찰되는 객체의 주요 차이점은, 관찰되는 객체 참조는 그것을 선언한 뷰가 소유하지 않으므로 사용되는 동안에(예를 들어, 뷰가 다시 렌더링된 결과) SwiftUI 시스템에 의해 파괴되거나 다시 생성될 위험이 있다는 것이다.
- @ObservedObject 대신 @StateObject를 사용하면 참조를 선언한 뷰가 참조를 소유하므로 해당 참조가 선언된 로컬 뷰나 자식 뷰에 의해 계속해서 필요로 하는 동안에는 SwiftUI에 의해 파괴되지 않는다.
- @ObservedObject를 사용할 특별한 이유가 없다면, 일반적으로 상태 객체를 사용하여 Observable 객체를 구독하는 게 좋다.
- 구문 측면에서 보면 둘은 완전히 상호 교환 가능하다.
import SwiftUI struct ContentView: View { @StateObject var demoData: DemoData = DemoData() var body: some View { Text("\(demoData.currentUSer), you are user number \(demoData.userCount)" } } State VS ObservedObject
1. @State는 뷰 한정, @ObservedObject는 외부 공유
@State
- 그 뷰 전용 데이터
- 부모 뷰가 사라지면 데이터도 같이 사라짐(수명 = 뷰의 수명)
- 작은 단발성 UI 상태 저장(토글, 텍스트 필드 값 등)
@ObservableObject + @ObservedObject/@StateObject
- 여러 뷰가 같이 쓰는 공유 데이터
- 객체로 따로 존재하므로 뷰가 사라져도 데이터는 유지 가능
- 시간에 따라 변하는 앱 전역 또는 모듈 단위 상태 관리
2. ObservableObject + @Published 동작 흐름
- ObservableObject 프로토콜 채택: "나는 바뀌면 알려줄 수 있는 객체다" 선언
- @Published 붙인 프로퍼티: 값이 바뀌면 자동으로 이벤트 발행
- 뷰에서 @ObservedObject나 @StateObject로 구독: 값이 바뀌는 이벤트를 받으면 자동으로 뷰 다시 그림
3. 구체적인 차이
@ObservedObject
- 외부에서 생성된 ObservableObject를 받아서 구독
- 소유권 없음 -> 뷰가 없어지면 참조도 사라짐
@StateObject
- 뷰에서 처음 객체를 만들 때 사용
- 소유권 있음 -> 뷰가 리렌더링돼도 객체는 유지
4. 비유
- @State: 방 안에 있는 메모지(그 방에서만 쓰임. 방이 사라지면 메모지도 같이 사라짐)
- @ObservedObject/@StateObject: 건물 공용 게시판(여러 방에서 볼 수 있음, 방이 사라져도 건물에 남아있음)
- @Published: 게시판에 새 글이 올라오면 건물 방송으로 알려줌(구독한 방 전부에게)