본문 바로가기

SwiftUI

[SwiftUI] 데이터 흐름에 대해 알아봅시다 (2/3) (@StateObject, @ObservedObject)

지난번에 이어 SwiftUI 에서 데이터를 관리하는 방법 중 @StateObject, @ObservedObject 에 대해 알아보도록 합시다.


1. @StateObject

 

정의를 살펴보면 관찰 가능한 객체를 인스턴스화 하는 프로퍼티 래퍼 라고 정의되어 있습니다.

인스턴스화 한다는 것은 '객체를 생성하고 메모리에 할당하는 것' 을 의미합니다.

 

 

 

@StateObject 를 사용할땐 단일 정보 소스로 사용하세요. 속성 선언엔 @StateObject 특성을 적용하고 ObserverableObject 프로토콜을 준수하는 초기 값을 제공하여 App, Scene, View 에서 상태 개체를 만듭니다.

 

 

뷰에서 생성된 데이터의 상태가 저장될 수 있으니 이렇게 생성된 하나의 데이터를 다른 곳에선 참조하는 식으로 사용하라는 것 같습니다. (혹시 이런 의미가 아니였다면 댓글로 정정 부탁드리겠습니다)

 

예를들어 Model 의 상태값들에 대한 관찰이 필요하다면 모델 선언시 ObservableObject 프로토콜을 추가해주고,

이 모델을 실제로 사용하는 곳에서 인스턴스화된 Model 을 사용하라는 의미로 생각하시면 됩니다. 

Model 의 상태값을 관찰을 할 땐 관찰을 원하는 프로퍼티 속성 앞에 @Published 를 붙여줘야 합니다!

이 내용이 공식 문서에 정확하게 작성이 되어 있지 않았네요.

 

지금까지의 내용을 예시로 살펴보면 아래와 같습니다.

 

 

1-1. 하위뷰와 상태 객체를 공유하는 방법

 

ObservedObject 특성이 있는 속성을 통해 상태 개체를 하위 뷰에 전달할 수 있습니다. 또는 위 코드의 MySubView와 같이 뷰에 EnvironmentObject(_:) 수정자를 적용하여 뷰 계층 구조의 환경에 개체를 추가합니다. 그런 다음 EnvironmentObject 속성을 사용하여 MySubView 또는 해당 하위 항목 내부의 객체를 읽을 수 있습니다.

 

위의 예제에서 살펴봤던 model 값을 어떻게 하위 뷰와 또 다른 뷰에 전달 할 수 있는지 알아봅시다. 

 

 

MySubView 를 만들때 관찰하고자 하는 model 객체 선언부에 @EnvironmentObject 적용하게 되면

MySubView 뿐만 아니라 또 다른 하위 항목에서도 관찰 가능한 형태가 만들어 집니다.

($ 기호 연산자를 사용했기에 양방향 연결이 완성된 것 또한 확인할 수 있습니다.)

 

1-2. 외부 데이터를 사용하여 State Object 를 초기화하는 방법

 

 

상태 객체의 초기 상태가 컨테이너 외부에서 오는 데이터에 따라 달라지는 경우 컨테이너의 초기화 프로그램 내에서 명시적으로 객체의 초기화 프로그램을 호출할 수 있습니다. 예를 들어 이전 예제의 데이터 모델이 초기화 중에 이름 입력을 사용하고 해당 이름에 대한 값을 뷰 외부에서 사용하려고 한다고 가정해 보겠습니다. 뷰에 대해 생성한 명시적 초기화 내에서 상태 개체의 초기화를 호출하여 이를 수행할 수 있습니다.

 

글을 읽긴했지만 이해하기 어렵네요... 코드를 통해 내용을 확인해보겠습니다.

위 내용을 확인해보면 @StateObject 를 통해 model 값을 관찰할 수 있는 코드와

init 생성자를 통해 model 에 값을 넣어주는 것 같은 코드를 확인하실 수 있습니다.

 

초기에 작성했었던 MyView 를 확인해보면 이땐 MyView 에서 인스턴스화를 진행해준 뒤

body 안에서 정해져 있는 값을 호출하는 형태로 사용했던 것을 확인할 수 있습니다.

이와 다르게 MyInitializableView 에서는 원하는 값을 넣어준 형태로 호출할 수 있는 구조로 사용되었다는걸 확인할 수 있습니다.

 

또한 @StateObject 를 사용할때 주의해야 할 것은 반드시 선언부에 private 을 넣어줘야 한다는 점 입니다.

SwiftUI 는 주어진 뷰에서 처음 호출할때만 @StateObject 를 초기화 하게 됩니다.

이때 private 을 사용하게 된다면 객체에게 안정적인 저장 공간을 제공할 수 있지만

private 설정이 없을 경우 예상하지 못했던 오류가 발생할 수 있다고 합니다.

 

1-3. View Identity 를 변경하여 강제로 다시 초기화 하기

(사실 이 부분에 대해선 명확하게 이해가 되질 않아 추후에 예시와 함께 설명을 작성하도록 하겠습니다..)

 

ForEach 내부에 뷰를 넣게 된다면, 암시적으로 데이터 요소를 식별할 수 있는 id 를 제공받게 됩니다.

 

다시 초기화를 하게되면 퍼포먼스 측면에서 문제가 있을 수 있으니 이를 유의해서 사용하라고 작성되어 있네요.

View identity 를 변경하는 과정에서 부작용이 발생할 수 있다는 것 또한 유의해야겠습니다.

 


2. @ObservedObject

정의를 살펴보면 관찰 가능한 객체를 구독하고 객체가 변경될때마다 뷰를 무효화하는 프로퍼티 래퍼라고 작성되어 있습니다.

뷰를 무효화한다는 것은 뷰를 다시 그린다는 것을 의미합니다.

(기존에 그려진 뷰를 무시하고 새롭게 업데이트한다고 생각하시면 될 것 같습니다.)

 

ObservedObject 를 사용하게 되는건 일반적으로 StateObject 를 하위뷰에 전달하기 위해 사용한다고 작성되어 있습니다.

아래의 예시를 통해 어떻게 사용되는지 확인해보도록 하겠습니다.

 

DataModel 에선 관찰가능한 객체로 사용하기 위해 ObservableObject 프로토콜을 채택하고 있고,

MyView 에선 model 을 상태 객체로 인스턴스화 하기 위해 @StateObject 선언자를 붙여줍니다.

하위뷰인 MySubView 에선 관찰된 모델을 받기 위해 @ObservedObject 선언자를 사용해줍니다.

 

추가적으로 유의해야할 점은 @ObservedObject 를 사용할땐 기본값이나 초기값을 지정하여 사용하는 것이 아닌

입력 역할을 하는 프로퍼티에만 속성을 사용해야 합니다.

 


참고자료

https://developer.apple.com/documentation/swiftui/stateobject#Force-reinitialization-by-changing-view-identity

 

StateObject | Apple Developer Documentation

A property wrapper type that instantiates an observable object.

developer.apple.com

https://developer.apple.com/documentation/swiftui/observedobject

 

ObservedObject | Apple Developer Documentation

A property wrapper type that subscribes to an observable object and invalidates a view whenever the observable object changes.

developer.apple.com