WWDC

[WWDC20] State, Binding, StateObject, ObservedObject

Phililip
728x90

안녕하세요.

 

이번에는 WWDC20의 'Data Essentials in SwiftUI' 영상을 보고 내용 정리해 볼게요.

 

(WWDC에서 예시로 든 샘플앱은 제가 나름대로 만들어서 github에 올려두었으니 참고해 주세요!)

https://github.com/phillip5094/WWDC-Example/tree/main/WWDC20/WWDC20-BookClub

 


# 개요

앱을 만들 때는 크게 3가지를 고려해야 합니다.

1) View에서 필요한 데이터가 무엇인가
2) View에서 데이터를 어떻게 조작하는가
3) 데이터는 어디서 오는가 (=> source of truth)

 

고려사항을 해결하기 위해서 앞으로 설명할 State, Binding, StateObject, ObservedObject 같은 것들이 사용됩니다.

 

# View에서 데이터를 수정하지 않는 경우

만약 View에서 데이터를 수정할 필요가 없다면 데이터를 let으로 선언합니다.

 

(위 코드를 기준으로) 데이터는 외부(superView)를 통해 전달됩니다.

 

 

# @State, @Binding

View 안에서 데이터를 수정하고 데이터가 View 내부에서만 사용될 땐 @State를 사용합니다.

(아래는 'Update Progress' 버튼을 눌렀을 때 하단에서 별도 View가 출력되도록 하는 예시입니다.)

 

[참고] State로 선언한 구조체는 렌더링이 끝난 후에도 SwiftUI가 구조체를 유지하고, 다시 렌더링이 필요할 때 구조체를 다시 초기화해서 기존 저장소에 다시 연결합니다.

 

 

하위 뷰에서 부모 뷰의 @State 프로퍼티 값을 수정하고 싶으면 @Binding을 사용합니다.

[주의] @State 프로퍼티를 하위 뷰에 그냥 전달하면 value copy가 되기 때문에 하위 뷰가 전달받은 구조체와 부모 뷰가 가지고 있는 구조체는 서로 다른 구조체가 됩니다.

 

 

Binding을 사용하면 @State 프로퍼티의 참조값을 공유하기 때문에 같은 데이터라고 볼 수 있어요.

 

 

# ObservableObject

위에서 설명한 @State와 @Binding은 View 자체에 대한 데이터를 관리하기 위해 사용합니다.

 

그러나 @State와 @Binding은 데이터의 lifecycle을 관리하고 동기화하기엔 적합하지 않습니다.

 

이럴 땐 @ObservableObject를 사용하는 것이 좋습니다.

 

 

@ObservableObject에 대해 좀 더 알아볼게요.

 

@ObservableObject는 참조 타입(클래스)에서만 사용할 수 있습니다.

 

@ObservableObject는 objectWillChange라는 프로퍼티를 제공하며, 이는 내부 값이 수정되기 전에 publisher를 방출합니다. 

 

ObservableObject는 기본 publisher를 제공합니다.

 

 

앱에 필요한 데이터를 value type으로 설계하고, View에 필요한 데이터들을 다수의 @ObservableObject로 만들어서 주입하도록 설계할 수 있습니다.

 

 

ObservableObject 값이 변경되고 변경된 값이 UI에 반영되려면 @Publisher를 사용합니다.

 

View에서 ObservableObject에 대해 의존성을 거는 방법은 총 3가지가 있습니다.

  • @ObservedObject
  • @StateObject
  • @EnvironmentObject

 

# @ObservedObject

View의 @ObservedObject 프로퍼티는 ObservableObject 인스턴스의 의존성을 갖습니다.

 

@ObservedObject 프로퍼티는 ObservableObject의 objectWillChange 프로퍼티를 구독하고 있어서, ObservableObject 값이 변경되면 View에도 반영되는 구조입니다.

 

[참고] 왜 값이 변경되기 전에 publisher를 방출할까?

SwiftUI가 값이 변경되기 전 타이밍을 알아야지 View 변경사항을 하나의 Update로 통합할 수 있기 때문입니다.

 

# @StateObject

@ObservedObject는 ObservableObject의 의존성만 가지기 때문에 View와 ObservableObject의 lifecycle은 무관합니다.

 

만약 ObservableObject의 lifecycle을 View와 연관시키고 싶다면 @StateObject를 사용합니다.

 

@StateObject 프로퍼티의 특징은 아래와 같습니다.

1) View가 인스턴스를 소유함
2) body가 호출되기 전에 인스턴스를 생성함
3) View가 살아있는 동안 인스턴스를 계속 가지고 있음. 즉, body가 다시 그려지는 상황이라도 데이터는 유지됨
4) View의 lifecycle에 맞춰서 인스턴스 생성 및 해제됨

 

 

# @EnvironmentObject

@ObservedObject를 사용하면 ObservableObject 객체를 최상단 뷰에서 생성하고 하위 뷰까지 계속 전달해줘야 하는데요.

 

@EnvironmentObject를 사용하면 인스턴스를 계속 전달할 필요 없이 하위 뷰에 의존성을 추가할 수 있습니다.

 

 

# Best performance

View는 단순히 UI를 정의한 것이며, SwiftUI는 이 정의를 사용하여 적절한 렌더링을 생성하는 것이에요.

 

View의 lifecycle은 View를 구성한 구조체의 lifecycle과는 별개입니다. 실제로 View 프로토콜을 준수하는 구조체의 lifecycle은 매우 짧다고 해요.

 

 

SwiftUI의 View 갱신에 대한 lifecycle은 아래와 같습니다.

 

 

 

이 cycle은 성능에도 중요한 영향을 끼칩니다.

 

만약 저 중에서 한 곳이라도 지연이 생기면 성능이 떨어지게 되는 것이죠. 이것을 'slow update'라고 부릅니다.

 

slow update를 피하는 방법에는 크게 3가지가 있습니다.

1) View 초기화 비용 적게 하기
2) View body를 순수 함수로 만들기
    - 단순히 View를 선언하고 return 하기
    - dispatching이나 다른 작업을 하지 않기
3) body가 언제 어떻게 호출되는지 예측하는 것 피하기

 

 

위에서 소개한 3가지 방법 중 첫 번째 방법에 관한 예시를 하나 보여드릴게요.

 

아래처럼 @ObservedObject를 사용해서 데이터를 생성할 경우, View body가 생성될 때마다 반복된 heap allocation이 발생하게 되어 slow update를 유발할 수 있습니다.

 

또한, View body가 생성될 때마다 새로운 인스턴스를 만들기 때문에 기존 데이터가 유지되지 않는 버그도 있습니다.

 

 

@StateObject를 사용하면 View body가 생성될 때 인스턴스가 유지되기 때문에 불필요한 heap allocation이 발생하지 않게 되어 성능에 도움을 줍니다.

 

또한, SwiftUI는 유저 상호작용, openURL와 같은 이벤트를 클로저로 처리함으로써 slow update를 피합니다.

 

다만, 해당 클로저는 main thread에서 수행되기 때문에 무거운 작업이 필요한 경우 background queue에서 수행하는 것이 좋습니다.

 

# 참고

https://developer.apple.com/videos/play/wwdc2020/10040

 

Data Essentials in SwiftUI - WWDC20 - Videos - Apple Developer

Data is a complex part of any app, but SwiftUI makes it easy to ensure a smooth, data-driven experience from prototyping to production...

developer.apple.com

 


이번 글은 여기서 마무리.

 

 

 

반응형