<h1><center> Data Flow Through SwiftUI [1주차] </center></h1>
###### tags: `💻 WWDC 스터디`
> [color=#724cd1][name=데릭]
> [Data Flow Through SwiftUI - wwdc](https://developer.apple.com/videos/play/wwdc2019/226/)
> WWDC 2019 Session 중 하나인 `Data Flow Through SwiftUI`에 대해 알아보자
# 개요
SwiftUI는 UI개발의 복잡성을 해결하기 위해 새롭게 고안된 툴이다.
## SwiftUI에서 살펴볼 사항들
**Principles of Data Flow**
- SwiftUI는 View의 계층에서 데이터를 Flow를 타는 툴이다.(View 계층에서 데이터가 왔다갔다 하는 것을 말한다.)
**Anatomy of an Update**
- SwiftUI는 항상 정확하고 일관된 데이터의 표현을 위해 뷰 계층을 업데이트한다. 내부적으로 어떻게 하는지 알아보자.
**Understanding Your Data**
- SwiftUI에서 제공해주는 Tool로 데이터에 어떤 게 적합한지 이해해보자.
## 데이터란?
SwiftUI에서 데이터는 `First Class Citizen`이다. 여기서의 데이터란 UI를 Drive하는 모든 정보들을 말하는 것이고, 다양한 형태를 가지고 있으며 모든 것이 데이터가 될 수 있다.
예) Toggle State, Message Model을 가지는 객체 등
## SwiftUI를 이해하기 위한 두가지 원칙
### 1. View에서 Data를 읽을 때마다 View는 Data에 의존성이 생긴다.
- 데이터가 변경될 때마다 뷰는 새로운 값을 반영해야하기 때문에 의존성이 발생한다.

> 예제그림 01) View에 Data 의존성 부여
`PlayerView`에서 `isPlaying`(Bool Type의 Property) 데이터를 읽어야한다고 가정해보자.
`isPlaying` 에서 데이터가 변경될 때마다 `PlayerView`를 업데이트해야한다.
SwiftUI이전에는 코드로 직접 업데이트를 처리해야 했지만 더 이상 데이터와 뷰의 동기화를 따로 해줄 필요가 없어졌다. SwiftUI는 선언적(Declarative)이기 때문에 Data Dependency(데이터에 대한 의존성)을 지침만 따르면 나머지를 알아서 처리해준다.
### 2. View의 계층에서 읽어들이는 모든 데이터들은 Source of Truth를 가진다.

> 예제 그림 02) SSOT
View에서 읽는 모든 데이터들은 결국 하나의 Source를 가진다는 뜻이다.
**NOTE**
> SSOT(Single Source of Truth): 데이터가 여러 곳에 존재하지 않고 오직 한 곳에서만 존재한다.
만약 데이터가 중복되어 존재할 경우, 항상 Sink를 맞춰야 한다. 일관적인 UI를 유지하는 것이 어렵고, 버그가 발생할 가능성이 높다.

> 예제 그림 03) 각각 Data를 가지고 있는 경우 Sink가 다를 수 있는 예제
> 예를 들면 위 그림에서는 View 두 개 각각을 위한 isPlaying 데이터가 각각 존재할 때, `Notification`, `KV observing`, 그 외의 다른 이벤트 등으로 하나의 `isPlaying` 값이 변할 경우에는 항상 다른 하나의 `isPlaying` 값도 싱크를 맞춰 변경되어야 한다. 이런 복잡성이 버그를 유발한다.

> 예제 그림 04) 상위 View가 변수를 가지고 있게 해서 하위 뷰의 변수들의 Sink를 맞추는 예제
```swift
final class VideoView: UIView {
private var topMenu = VideoTopMenuView(frame: .zero)
private var playControl = VideoPlayControlView(frame: .zero)
/// 각 서브 뷰에서 isPlaying 변수를 가지고 있을 때, 이 데이터들의 싱크가 맞아야 일관된 UI를 가질 수 있다.
var isPlaying: Bool = false {
didSet {
self.topMenu.isPlaying = self.isPlaying
self.playControl.isPlaying = self.isPlaying
}
}
}
```
## @State

> 예제 그림 05) 팟케스트 플레이어
```swift
struct PlayerView: View {
let episode: Episode
@State private var isPlaying: Bool = false
var body: some View {
VStack {
Text(episode.title)
Text(episode.showTitle)
.font(.caption)
.foregroundColor(.gray)
Button(action: {
self.isPlaying.toggle()
}) {
Image(systemName: isPlaying ? "pause.circle" : "play.circle")
}
}
}
}
```
@State 프로퍼티 랩퍼를 적용해서 `해당 프로퍼티의 값이 변경될 수 있으며, 데이터 값에 따라 Self(PlayView)의 상태가 변경될 수 있다`를 시스템에 알린다.

> 예제 그림 06) 부모 뷰가 State 프로퍼티를 가질 때
@State를 사용하면, `isPlaying` 변수를 영구적인 저장공간에 할당하여 시스템이 의존성을 가지게 되어 추적하게 된다. 일반적인 뷰는 시스템에 의해 자주 재생성되지만, @State가 붙으면 뷰가 여러 번 업데이트되어도 계속 저장공간에서 데이터 값을 유지한다.
- 뷰 안에서만 `isPlaying`의 상태를 관리한다는 것을 확실히 하기 위해 private으로 처리한다.
@State 변수의 값이 변할 때 뷰의 렌더링이 달라져야 한다는 것은 SwiftUI에서 감지하게 된다.

> 예제 그림 07) 부모 뷰와 하위 뷰 Sink
**`isPlaying`의 값이 `True`로 변하면 어떻게 될까?**

> 예제 그림 08) isPlaying True일 때
유저의 인터랙션에 따라 재생 버튼의 액션이 실행되고, isPlaying 이라는 State 변수 값이 변하고, State가 변경되었으므로 뷰 자신과 자식 뷰들의 Body가 모두 새롭게 랜더링된다. (Button > isPlaying 값 변경 > PlayerView > 자식뷰인 Text, Text, Button 에 각각 전파 > 전체적인 뷰가 갱신됨) 단, 프레임워크에서 오직 변경된 것에 대해서만 다시 렌더링해주기 때문에 이 과정은 효율적으로 수행된다.
이런 Flow를 Framework가 Dependency를 관리해준다고 하는 것이다.
**NOTE**
> State를 정의할 때마다, 뷰가 소유하는 Source of truth가 새롭게 하나 정의된다는 것을 기억하자.
> 뷰는 더이상 연속된 이벤트의 집합이 아니다. 상태를 가지는 함수이다.
이전 방식은 직접적으로 View 계층을 변경하는 방법으로 이벤트에 응답했다.
예) 유저의 인터랙션이 발생할 때마다 직접 서브뷰를 추가/제거하거나 뷰의 알파값을 변경하는 코드를 실행하는 방식.
**SwiftUI**

> 예제 그림 09) SwiftUI에서 State의 역할
단순히 상태(State)만을 변경하면 된다. State가 Source of truth로써 동작하며, 자연스럽게 뷰가 변경된다. 이 부분이 선언적 구문이 빛나는 부분이다.
### 예제 코드
```swift
// 기존의 완전히 수동적인 방식
class SampleView: UIView {
var isHiddenCircle: Bool = false {
didSet {
self.circle.isHidden = self.isHiddenCircle
}
}
var isHiddenRect: Bool = false {
didSet {
self.rect.isHidden = self.isHiddenRect
}
}
}
// 위를 조금 더 개선한 방식
class SampleView: UIView {
var isHiddenCircle: Bool = false {
didSet { self.updateAppearance() }
}
var isHiddenRect: Bool = false {
didSet { self.updateAppearance() }
}
private func updateAppearance() {
self.circle.isHidden = self.isHiddenCircle
self.rect.isHidden = self.isHiddenRect
}
}
// SwiftUI 방식
// 바로 위 코드와 비교해보면 원리는 같지만 좀 더 선언적인 문법, 효율성을 지님.
// 또한 @State & body 규약을 통해 예측 가능한 가독성 있는 코드이기 때문에
// 기존의 코드 룰을 모르는 제3자가 이 뷰를 건드릴 때에도 실수를 방지할 수 있다
// 이렇게 문법적으로 규약을 만들어 놓는 것의 장점이라고 생각한다
struct SampleView: View {
@State var isHiddenCircle: Bool = false
@State var isHiddenRect: Bool = false
var body: some View {
Circle()
.isHidden(self.isHiddenCircle)
Rectangle()
.isHidden(self.isHiddenRect)
}
}
```
## Binding
예제에서 여러 개의 재사용 가능한 컴포넌트로 UI를 분리해보자.
```swift
struct PlayerView: View {
let episode: Episode
@State private var isPlaying: Bool = false
var body: some View {
VStack {
Text(episode.title)
Text(episode.showTitle)
.font(.caption)
.foregroundColor(.gray)
Button(action: {
self.isPlaying.toggle()
}) {
Image(systemName: isPlaying ? "pause.circle" : "play.circle")
}
}
}
}
// -> Button을 분리하자
struct PlayButton: View {
@State private var isPlaying: Bool = false
var body: some View {
Button(action: {
self.isPlaying.toggle()
}) {
Image(systemName: isPlaying ? "pause.circle" : "play.circle")
}
}
}
```
**문제점**
이렇게 분리하면 PlayButton의 isPlaying 프로퍼티는 또 다른 `Source of truth`를 만든다.
- PlayButton의 State 프로퍼티는 SuperView인 PlayView의 State와 항상 싱크가 되도록 구성한다. (isPlaying이라는 상태 데이터는 한 번에 한 곳에만 존재하면 되는 데이터이다.) PlayButton은 자신의 isPlaying을 따로 소유할 필요가 없으며, 단순의 PlayView가 소유하는 isPlaying 프로퍼티를 읽거나 변경할 수 있으면 된다.
```swift
struct PlayButton: View {
@Binding var isPlaying: Bool
var body: some View {
Button(action: {
self.isPlaying.toggle()
}) {
Image(systemName: isPlaying ? "pause.circle" : "play.circle")
}
}
struct PlayerView: View {
let episode: Episode
@State private var isPlaying: Bool = false
var body: some View {
VStack {
Text(episode.title)
Text(episode.showTitle)
.font(.caption)
.foregroundColor(.gray)
PlayButton(isPlaying: $isPlaying) }
}
}
```
**NOTE**
> PlayButton은 isPlaying 값의 복사본을 가지는 것이 아니라, 단순히 PlayView(SuperView)의 isPlaying 프로퍼티를 참조하는 것이다. 따라서 프로퍼티 간의 싱크를 신경쓸 필요가 없다.
> SuperView입장에서 보면, Binding은 자신의 State 프로퍼티에 대한 접근을 다른 컴포넌트에게 허용하는 것이다.

> 예제 그림 09) SwiftUI이전의 방식
뷰 컨트롤러의 서브 뷰들이 동일한 프로퍼티를 각각 들고 있는 방식이다. 이런 구조에서는 뷰 컨트롤러에서 데이터의 값이 변경되면 자신의 모든 서브 뷰들에게 새로운 값을 셋팅해주는 과정(데이터 싱크를 맞추는 과정)이 필요하다.

> 예제 그림 10) SwiftUI 방식
데이터 Dependency를 정의하면 프레임워크가 나머지를 알아서 처리한다.

> 예제 그림 10) SwiftUI Binding 예제 코드
```swift
// UIKit의 UISlider
class SampleView: UIView {
var sliderValue: Float = 0 {
// UI 싱크를 맞추기 위해 아래와 같은 처리들을 하는 경우가 종종 있었다
didSet { self.slider.value = self.sliderValue }
}
private let slider = UISlider(frame: .zero)
}
// SwiftUI의 Slider
struct SampleView: View {
// 이 값이 변경되면 자동으로 UI도 갱신된다
@State var sliderValue: Float = 0
var body: some View {
Slider(value: $sliderValue)
}
}
```
## Working With External Data

> 예제 그림 11) SwiftUI 외부 데이터
SwiftUI에는 데이터 관리를 위한 많은 방법들이 있다.
@Environment, ObservableObject에 대해 알아보자.
- Timer fired > State 변경
- Notification > State 변경
- 그 외 등등 > State 변경

이처럼 SwiftUI에서는 외부 이벤트라도 결국 State의 변화로 이어진다. 즉, 앞의 예제에서 유저의 interaction(Action)이 발생해 State가 변경되었던 것과 결과적으로 차이가 없다.
- 이런 외부 이벤트들을 Publisher라고 한다. Publisher는 Combine Framework로부터 발생된 것이다.
- Combine은 시간에 따라 값을 처리하기 위해 선언적인 API
## Publisher 적용
```swift
struct PlayerView: View {
let episode: Episode
@State private var isPlaying: Bool = false
@State private var currentTime: TimeInterval = 0.0
var body: some View {
VStack {
Text(episode.title)
.foregroundColor(isPlaying ? .white : .gray)
Text(episode.showTitle)
.font(.caption)
.foregroundColor(.gray)
PlayButton(isPlaying: $isPlaying)
Text("\(currentTime, formatter: currentTimeFormatter)")
}
.onReceive(PodcastPlayer.currentTimePublisher) { newCurrentTime in
self.currentTime = newCurrentTime
}
}
}
```
VStack 하단에 onReceive modifire를 추가하였다. 이제 타임스탬프가 변경되면 onReceive 블럭 안에서 currentTime 이 새로운 값으로 변경될 것이다. currentTime 은 State 변수이다. 따라서 State가 변경된 것으로 취급되고, body 를 통해 뷰의 렌더링이 다시 이루어진다. 그러면 currentTIme 에 대한 UI 갱신이 자동으로 이루어진다. 이런 과정을 거치는 동안 수동으로 작성된 코드나 invalid 체크 코드는 필요하지 않다는 것이 장점이다.
NOTE
>부연설명: onReceive 는 Combine에서 제공해주는 것으로, Publisher가 receive(on:) 메서드를 통해 특정 큐/쓰레드에서 돌도록 지정한 것을 여기서 받아 처리할 수 있게 된다. 위 코드는 현재 타임스탬프가 변경될 때 fire되는 Publisher가 미리 작성되어 있다는 가정 하에 작성된 것이다.
## ObservableObject
- ObservableObject 는 이미 소유/관리중인 모델이 있을 경우, 이 모델과 뷰의 동기화를 편하게 만들어주는 프로토콜들이다.
- 데이터가 변경되었을 때, 그 상태를 알릴 필요가 있는 모델이라면(=값의 변경을 외부에서 감지할 필요가 있는 모델) ObservableObject 프로토콜을 따르도록 하자.
```swift
protocol ObservableObject : AnyObject
```
```swift
class PodcastPlayerStore {
var currentTime: TimeInterval
var isPlaying: Bool
var currentEpisode: Episode
func advance() { ... }
func skipForward() { ... }
func skipBackward() { ... }
}
```
- 싱크를 맞추기 위해 사용할 모델을 하나 정의하였다. 이제 이 모델을 ObservableObject 프로토콜에 따르게 해보자.
> 이 프로토콜이 요구하는 것은 데이터가 변경되었을 때를 위한 Publisher이며 objectWillChange 프로퍼티에 이 Publisher를 정의해두어야 한다.
```swift
class PodcastPlayerStore: ObservableObject {
// ...
var objectWillChange = PassthroughSubject<Void, Never>()
func advance() {
self.currentEpisode = nextEpisode
self.currentTIme = 0.0
// Notify subscribers that the player changed
self.objectWillChange.send()
}
}
```
위 코드에서는 objectWillChange 프로퍼티에 PassthroughSubject 라는 Publisher를 하나 정의하였고, 데이터의 변경이 발생하는 advance() 메서드 안에서 이 Publisher의 send() 를 호출해주고 있다. 이 Publisher를 subscribe 하여 데이터의 변경 시점을 정확히 알 수 있게 된다. 즉, 언제 뷰를 업데이트해야 할지 알게 된다는 것이다.

> 예제그림 12) ObservedObject
> View는 @ObservedObject라는 Property Wrapper를 사용해 ObservableObject Model에 의존할 수 있다. Automatic dependency tracking(모델이 변경될 때 뷰가 알아서 변경됨) 관점에서만 보면 앞서 다루었던 @State 와 다르지 않다.
> MVVM 예제코드를 보면 ViewModel이 채택하고 있음
[SwiftUI - MVVM](https://github.com/stevencurtis/SwiftCoding/tree/master/SwiftUI/MVVMNetworking/MVVMNetworking)
```swift
struct MyView: View {
@ObservedObject var model: PodcastPlayerStore
...
}
MyView(model: modelInstance)
```
MyView는 PodcastPlayerStore모델의 상태가 변경될 때(ex: 다음 에피소드 재생을 위한 advance 함수가 불렸을 때) 자동으로 그것을 알아채고 뷰를 업데이트할 수 있게 되었다.
## EnvironmentObject
SwiftUI의 Environment는 뷰 계층에게 데이터를 넣어주는 캡슐화 방법이다.

> 예제그림 13) EnvironmentObject
`.environmentObject(_:)` 를 통해 PodcastPlayerStore를 Environment로 만들고, MyView 에서는 @Environment Property Wrapper 를 붙여서 이 모델에 대한 의존성을 정의해둘 수 있다.
```swift
@frozen @propertyWrapper struct EnvironmentObject<ObjectType> where ObjectType : ObservableObject
```
```swift
struct PlayerView: View {
@EnvironmentObject var player: PodcastPlayStore
var body: some View {
VStack {
Text(player.currentEpisode.title)
.foregroundColor(isPlaying ? .white : .gray)
Text(player.currentEpisode.showTitle)
.font(.caption)
.foregroundColor(.gray)
PlayButton(isPlaying: $player.isPlaying)
Text("\(player.currentTime, formatter: playheadTimeFormatter)")
}
}
}
```
> ObservedObject로 사용하던 때와 마찬가지로, player 프로퍼티의 상태가 변경될 때마다 뷰가 자동으로 업데이트 될 것이다.
**언제 ObservedObject를 쓰고 언제 EnvironmentObject를 쓰면 좋은 것일까**
- 기본적으로는 ObservedObject를 사용하면 된다. 그러나 때때로 모델이 변경되었음을 여러 뷰를 거치며 전달해야 할 상황이 있을 것이다.
간단히 예를 들어 아래와 같은 뷰 계층이 있다고 해보자.

> 예제 그림 13) EnvironmentObject
특정 모델이 변경되었을 때 초록색 뷰들이 업데이트되어야 한다면, 모델이 변경되었음을 전달하기 위해 실제로는 그 정보가 필요하지 않은 파란색 뷰들을 경유해야 할 것이다. 이런 케이스에서 EnvironmentObject를 사용하면 깔끔하게 처리할 수 있다.

## 어떤 것을 어디에 적용하면 좋을까?
1. 데이터의 생성/관리 주체 관점에서 생각하면 다음과 같이 나눠볼 수 있다.

**@State**
- Value Type
- 뷰의 로컬 데이터 (내부 프로퍼티)
- 뷰에서 소유되고 관리되어야 하는 데이터
- (ex) SwiftUI 프레임워크의 Button의 highlight 여부는 Button 내부에서 @State로 관리되고 있다. Touch 중일 때 highlighted 상태, Touch가 끝났을 때 highlighted 가 아닌 상태라는 것을 외부에서 관리할 필요없이 Button 내부에서만 관리하면 되기 때문이다.
**ObservableObject**
- Reference Type
- 이미 관리(소유)하고 있는 데이터에 적용하면 좋음
- 외부로부터의 데이터를 표현할 때
2. 재사용가능한 컴포넌트를 만드는 경우도 생각해보자. 이 경우에는 데이터의 성격이 Read-only인지 Read-write인지에 따라 다음과 같이 나눠볼 수 있다.

**Read-only 데이터**
- 재사용을 위한 뷰에서 굳이 데이터를 변경할 필요가 없는 경우
- Swift Property, Environment 등을 사용하기 (데이터 변경 시, 감지하여 알아서 뷰가 업데이트 됨)
**Read-write 데이터**
- 재사용을 위한 뷰에서도 데이터를 변경할 일이 있는 경우 (ex: SwiftUI의 Toggle)
- @Binding을 사용하기 (값을 소유하지 않으면서도 읽고 쓸 수 있음)
- 예로 든 Toggle의 경우, on/off 데이터가 어디서 발생되었고 누가 관리하는지 Toggle View 자체에서는 알 필요가 없다. 따라서 isOn 여부를 Binding으로 받고 있는 것이다(참고).