[SwiftUI][concept] SwiftUI에서 상태와 데이터 흐름을 제어하는 방식에 대하여

Uno·2021년 12월 21일
0

SwiftUI

목록 보기
11/30

서론


SwiftUI의 핵심요소인 “@State”과 “@Binding”에 대해서 작성하려고 합니다. 스유를 공부하다보니, 각 View 간의 데이터 전달을 위 두 개의 개념을 통해서 하더라구요. 공부하고 보니 정말 짧은 코드로 편리하게 사용할 수 있는 것 같아서 이번 기회에 정리해보고자 합니다. 그 전에, @State와 @Binding에 대하여 작성된 공식문서의 상위 문서가 “State and Data Flow” 입니다. 어떤 맥락에서 작성되었는지 알아보고 두 개념에 대해서 알아보면 좋을 것 같습니다.

State and Data Flow


State and Data Flow 문서 링크: Apple Developer Documentation


공식문서에 이렇게 적혀있습니다.

앱 내의 모델들의 변화와 데이터 흐름을 제어하거나 그에 맞게 응답할 수 있습니다.

이 문서에서는 “데이터의 흐름” 이 키워드가 될 것 같고, 그에 대한 변화가 주제가 될 것 같습니다.
같은말인가?..

SwiftUI 에서 UI를 구성하면, 뷰 안에 뷰가 들어가 있고, 그 뷰 안에 또 다른 뷰가 들어가 있는 구조로 구성하게 됩니다. 마치 UIKit에서 ViewController 내부에 다른 UIView 를 생성하기도 하고 UIView의 서브 클래스된 다른 객체를 생성하는 것처럼요.

SwiftUI에서는 뷰안에 뷰를 구성함과 동시에 데이터도 계층에 맞게 구성합니다. (물론 SwiftUI만의 특징이라고 볼 순 없겠죠.)

다른 프레임워크와 다른 점이라면, 상위 계층의 뷰에서 변경된 데이터 혹은 하위 계층에서 “사용자의 응답” 에 따른 데이터 변경이 발생하면, UI를 “자동적으로” 변경합니다.
(UIKit으로 설명하자면 Observable 객체를 만들어서 하는 “데이터바인딩” 과 같은 느낌)


이 그림 중 “User” 를 먼저 보겠습니다.

  • User -> Action -> State -> View -> (랜더링) -> User
    => 사용자가 버튼을 클릭하면, 어떤 상태를 변경시키고 그 변경은 뷰를 변경하고 뷰를 변경한 걸 사용자에게 다시 랜더링해서 보여준다.

이렇게 설명할 수 있겠죠. 단순하게 생각하면, 회원가입 “완료” 버튼을 클릭하면, 완료와 함께 다른 화면으로 이동하는 그런 상황입니다.

  • External event -> Action -> State -> View -> 랜더링 -> User
    => 외부의 이벤트로 인해서 액션이 동작하고 그것이 상태변화를 만들며, 뷰의 값을 변경하고 변경된 뷰에 맞게 그것을 화면에 보여주어 사용자에게 알려준다.

예를들면, 알림을 설정했는데 알림울릴 시간에 도달했습니다. 그러면 알림이 휴면상태가 아닌 알림 울리는 상태로 변경이 되겠고, 그것에 따라 보여주는 화면이 달라지겠죠. 최종적으로는 사용자가 달라진 화면을 볼겁니다.

여기까지만 보면, UIKit에서도 하던 방식입니다.

맞습니다. SwiftUI 라고 아예 다른 절차로 진행되는 것은 아니죠. 다만, 이를 어떻게 구현하는지가 다를 뿐입니다.

SwiftUI 에서는 오늘 알아볼 두 개념인 (@State / @Binding) 을 통해 구현합니다. 이걸 앱의 데이터와 UI를 연결한다고 합니다. (말 그대로 데이터(와 UI를) 바인딩 하는 겁니다.)

A single source of true

SwiftUI에서 자주 쓰이는 말인 “A single source of true” 라는 말이 여기서 나옵니다.
데이터를 연결한다는 것은 두 개의 데이터가 있다는 것이죠? 그리고 여기서 두 데이터는 분명한 계층 혹은 계급이 존재합니다. 원천 데이터의 역할은 하는 하나의 데이터가 있고, 그 데이터에 따라서 함께 반응하는 데이터의 관계입니다.

여기서 개념을 1:1 매칭해보면,
“원천 데이터” == “@State”
그리고
“데이터에 따라 함께 반응하는 데이터” == “@Binding”
입니다.

이 둘을 활용하면, 코드의 양을 상당히 줄일 수 있고, 사용하기도 편합니다. 실제로 상당히 편해서 UIKit에서도 사용하고 싶을 정도입니다.

마저 문서를 살펴볼게요.

SwiftUI 에서 데이터 흐름에 대한 개요

  1. @State 를 통해 특정 값타입을 래핑(랩으로 음식을 감싸다 할 때, 래핑)하여, UI 상태를 “로컬”에서 관리합니다.
    -> State를 이용하면, 로컬에서 잠시 값을 저장하고 있다가, UI 해당 값의 변화에 맞춰 저장된 값들을 UI를 모두 다시 랜더링하게됩니다. 즉, 개발자는 그냥 값을 감싸주기만 하면, SwiftUI가 알아서 데이터를 자동으로 변경해준다는 뜻입니다.

  2. 외부 이벤트 혹은 따로 만든 모델의 데이터와 UI를 연결하고 싶을 땐, “ObservableObject”를 해당 모델에서 채택하고, 프로퍼티를 래핑하는 것은 “ObservedObject”로 감쌉니다. “ObservableObject”는 Envirnoment안에 있는 “EnvironmentObject” 를 사용하여 접근할 수 있습니다. 만약, “ObservableObject” 를 직접 생성하고 싶다면, (모델을 통해서가 아니라) @StateObject 를 감싸면 됩니다.
    -> 자세히는 다른 글에서 다룰 예정입니다만, 퉁쳐서 이해하보면, 보통 MVC나 MVVM으로 프로젝트를 구성하게 될텐데, 그 중 Model에 있는 데이터도 UI 와 데이터를 바인딩할 수 있다는 겁니다. 그 데이터를 어떤식으로 선언하는지 어떤식으로 사용하는지에 대한 설명입니다.

  3. 원천 데이터와 그것에 맞게 대응하도록 하는 데이터를 연결하는 방법은 @Binding 을 통해서 할 수 있습니다.

  4. 앱 전반적으로 사용되는 데이터를 관리하고 데이터를 다른 곳으로 보내고 싶다면 “Environment” 를 사용하면 됩니다.
    -> @State의 범위를 확장했다고 보면 되겠습니다.

  5. (이해가 안됨 유의) “PreferenceKey” 를 통해서 뷰의 계층구조에 맞게 데이터를 전달합니다.
    -> 정확하진 않지만, 제 느낌으로는 위 프로토콜을 통해서 내부적으로 데이터를 바인딩하고 있다는 뜻 같습니다. 즉, 구동 원리에 대한 프로토콜이 무엇인지 알려주는 것 같네요.

  6. (이해가 안됨 유의) FetchRequest를 사용하여 Core Data에 저장된 영구 데이터를 관리합니다.
    -> 아마도 코어데이터에 있는 데이터도 연결시킬 수 있는 것 같습니다.

예시

이렇게 특정 상태를 정의하는 변수 좌측 끝에 “@State” 라고 작성해주면, 래핑은 끝입니다.

@State private var isVisible = true

이제 “isVisible” 변수가 바뀌게 되면, 하위에 연결된 (바인딩) 다른 뷰들을 다시 그릴 겁니다.

하지만, 직접 접근하게 되면, 해당 값으로 현재 할당된 “true” 를 가져와서 동작합니다.
SwiftUI 가 아닌 UIKit에서 일반적으로 하는 방법이죠.

if isVisible == true {
	Text("Hello")
}

SwiftUI의 특징인 데이터 바인딩을 통해서 전달하는 방법은 아래와 같습니다.

Toggle("Visible", isOn: $isVisible)

Toggle이라는 뷰에 “$” 와 함께 전달했습니다. 이 경우가
@State와 @Binding 을 연결한 경우입니다.

이제 State에 있는 isVisible 이 변함에 따라서 Toggle에 isOn 데이터가 변경될 것이고, 랜더링을 다시해서 UI를 업데이트 할 겁니다. 역으로 사용자가 isOn값이 변경되면, State의 값이 변경되겠죠. 그리고 역시 UI를 다시 그릴겁니다.

정리


공식문서를 한 줄 한 줄 읽으면서, 개념을 살펴보니 공식문서가 참 자세히 잘 설명되어있다는 것을 다시 느낄 수 있었습니다.
아직 코드를 작성하지 않아서 와닿지 않을 수 있습니다.

“데이터를 연결하기 위해 SwiftUI에서는 프로퍼티 래퍼를 사용한다.”

이 정도로 기억하시고, 이후 어떤식으로 활용되는 지는 다른 글에서 작성하도록 하겠습니다.

읽어주셔서 감사합니다.

참고자료


Apple Developer Documentation

profile
iOS & Flutter

0개의 댓글