apple/SwiftUI, Combine

SwiftUI State and Data Flow

lgvv 2022. 6. 2. 17:14

SwiftUI State and Data Flow

 

 

(공식문서 링크)

https://developer.apple.com/documentation/swiftui/state-and-data-flow

 

Apple Developer Documentation

 

developer.apple.com

 

 

Overview

공식문서의 설명

 

  • SwiftUI는 user interface 디자인을 선언적으로 제공하고 있음. (선언형 프로그래밍)
  • 따라서 View를 계층적으로 구성할 때 의존성도 View의 계층 구조에 맞춰서 나타남.
  • 외부 이벤트 혹은 user 액션에 의해 데이터의 변했을 때, SwiftUI에 영향을 받는 부분을 자동으로 업데이트.
    • 값이 달라지지 않으면 렌더링 안하는데, 달라지지 않아도 POD 규칙에 의해서 렌더링 할 수도 있음.
  • 결과적으로 이 프레임워크(SwiftUI)는 전통적으로 ViewController에서 하는 대부분의 일을 수행

 

UIKit으로 코드 작성하던거 SwiftUI로도 대부분 작성할 수 있다는 의미

 

  • SwiftUI에는 @State 및 @Binding을 제공하여 user interface에 연결
  • 이러한 것들은 앱의 모든 데이터에 대한 단일 소스를 유지하는데 도움을 주며, 작성해야 하는 로직의 양을 줄여줌
    • 즉, 명령형은 데이터 바뀌고 레이아웃 업데이트 하라고 명령해야 하는데 그런게 없어진다는 의미

 

 

애플 공식문서

 

 

SwiftUI의 LifeCycle

  • SwiftUI에는 View의 상태를 나타내는 함수가 단 2개 존재
    • onAppear, onDisappear
  • 대신에 상태를 나타내는 @State 등 다양한 프로퍼티 래퍼가 존재해 데이터 흐름에 대한 여러 상태를 대응할 수 있음.
.onAppear {
    print("ViewAppeared") // viewDidAppear
}

.onDisappear {
    print("ViewDisappeared") 
}

 

 

@State

  •  struct는 value type이라서 값을 변경할 수 없으나, SwiftUI에서 @State를 제공해 strcut내의 값을 변경할 수 있음.
    • mutating과 비슷.
  • SwiftUI에서 일반적으로 View를 struct로 생성하는데 값을 저장해야 하는 경우에는 class로도 생성할 수 있음.
    • 이 경우는 UIRepesentView로 사용
  • 일반적으로 @State는 private으로 선언되고, 다른 View와 공유하지 않음.
    • 공유 가능한 상태가 있으면 Store를 외부로 분리하고 거기서 관찰하는 구조가 더 나은 설계
  • View와 공유하고 싶다면, @StateObject나 @ObservedObject를 사용하기


@Binding

  •  @Binding은 부모 View의 @State와 같은 같을 양방향으로 연결되게 도움.
    • 양방향이라서 하위에서도 부모의 @State를 변경할 수 있음.
    • (개인의견) 이러면 Action을 단방향으로 흘려서 관리하는게 아니라 상태 관리가 복잡해질 거 같음.
  • @Binding의 경우에는 init을 통해서 사용하는게 개인적으로 깔끔한데,
    • init에서 사용할 시 .constant()로 초기값을 지정할 수 있습니다.

 

샘플코드

샘플 코드는 Combine을 몰라도 간단히 사용할 수 있음.


최근에 SwiftUI와 MVVM에 대한 논의가 뜨거운데 이 부분이 다른 주제(AI, 블록체인 등)에 비해서 크게 논쟁할 일인지는 잘 모르겠음.

코드 스타일의 변화는 있을 수 있고 팀 내에서 서비스나 상황에 맞게 코드를 작성하면 되고 진짜 핵심인 AI나 블록체인 등에 기술에 대해서 더 학습하고 적용하는 부분에 더 많은 관심이 쏠렸으면 함.

 

 

struct ContentView: View {
  @State private var showAddView = false

  var body: some View {
    VStack {
      Text("Hello World.")
    }
  }
  .sheet(isPresented: $showAddView) {
     AddView(isPresented: self.$showAddView)
  }
}

struct AddView: View {
  @Binding var isPresented: Bool

  var body: some View {
    Button("Dismiss") {
       self.isPresented = false
    }
  }
}

 

 

 

 

ObservableObject

Property Wrapper가 아닌 protocol임.

요즘 `@` 표시만 보이면 자꾸 헷갈려서 안에 한번 더 까고 들어가서 보는거 같음.

객체가 변경되기 전에 방출하는 publisher가 방출하는 객체의 타입

 

 

  •  ObservableObejct는 Protocol이며, Combine 프레임워크의 일부
  • 이를 사용하기 위해서는 프로토콜을 준수하고, @Published를 사용하여 상태를 관찰 가능하게 함.
  • @Published를 사용하면 변수의 값이 추가되거나 삭제되었다는 것을 view가 알 수 있게 해줌.
  • ObservableObject protocol은 ObservedObeject property wrapper를 이용하여 외부 참조 모델에 연결


샘플 코드 1 (애플 공식문서)

class Contact: ObservableObject {
    @Published var name: String
    @Published var age: Int

    init(name: String, age: Int) {
        self.name = name
        self.age = age
    }

    func haveBirthday() -> Int {
        age += 1
        return age
    }
}

let john = Contact(name: "John Appleseed", age: 24)
cancellable = john.objectWillChange // 🟠 객체가 변경되기 전에 방출합니다.
    .sink { _ in
        print("\(john.age) will change") 
     }
print(john.haveBirthday())
// Prints "24 will change"
// Prints "25"

 

샘플 코드 12(애플 공식문서)

모델 자체에 바인딩하면 좋은데, 근데 북마크 같은 값이 바뀌어도 리스트 전체가 리렌더링 되어서 이건 별도로 모델을 분리하는 노력이 필요할 거 같음.

(웹에서 렌더링 개선할 때 주로 하던 작업들)

class MyViewModel: ObservableObject {
  @Published var dataSource: MyModel
  
  init(dataSource: MyModel) {
    self.dataSource = dataSource
  }
}

 

@StateObject

  • WWDC2020에서 @StateObject를 추가적으로 공개
  • @ObservedObject와 거의 같은 방식으로 작동
  • SwiftUI가 View를 다시 렌더링 할 때, 실수로 취소되는 것을 방지
  • View에서 @StateObject를 이용하여 직접 관찰 가능한 객체를 인스턴스화.

 

 

struct ContentView: View {
  @StateObject var user = User()
}



@ObservedObject

ObservableObject랑 다르고, 얘는 구조체임.

관찰 가능한 객체에 구독하고, 객체가 변경될 때마다 view를 무효화합니다.(update한다는 뜻)

 

  • SwiftUI는 @ObservedObject를 통해 view가 외부 객체를 감지할 수 있게 해줌.
  • 아래 코드에서 User Class는 ObservableObject를 준수하고 @Published 변수를 가짐
  • @ObservedObject의 user 변수는 이러한 User class 객체를 담고
  • SwiftUI는 이러한 user 객체의 @Published 변수 값이 변경될 때 view를 refresh

 

class User: ObservableObject {
  @Published var name = "Hohyeon Moon"
}

struct ContentView: View {
  @ObservedObject var user = User()

  var body: some View {
    VStack {
      Text("Your name is \(user.name).")
    }
  }
}

 

 

@EnvironmentObject

  • @EnvironObject는 보통 앱 전반에 걸쳐 공유되는 데이터에 사용
  • @EnvironObject는 .environmnetObejct()를 통해 값이 전달할 수 있음.
  • 전달하는 Object는 ObservableObject 프로토콜을 채택해야 함.
  • 아래 코드와 같이 rootView를 제공하면, 어떠한 View에서도 사용이 가능
  • EnvironmentObject property wrapper를 통해서 environmnet에 저장된 관찰 가능한 객체에 접근 가능

 

 

SwiftUI의 SceneDelegate는 더 이상 기본적으로 Xcode에서 생성되지 않음.

이거 근데 필요하면 WidnowGroup에서 modifier 이용하면 됨.

근데 AppDelegaet 필요하면 수동으로 생성해서 연결해야 함.

 

 

더이상 SceneDelegate가 default로 생성X

 

샘플 코드

간단한 예시.

import SwiftUI

// MySettings.swift
class Settings: ObservableObject {
  @Published var version = 0
}

@main
struct CombinePracticeApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView().environmentObject(Settings())
        }
    }
}

struct ContentView: View {
    @EnvironmentObject var settings: Settings
    
    var body: some View {
        Text("Hello, world!")
            .padding()
        
        Button {
            settings.version += 1
        } label: {
            Text("version \(settings.version)")
        }

    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

 

 

 

ObservableObject와 EnvironmentObject 비슷하지만 다른 차이점 보고가기

 

정의 위치 데이터 모델을 정의하는 프로토콜 SwiftUI 환경(Environment)에 주입되는 객체
역할 데이터 변경을 감지하고 뷰를 업데이트 여러 뷰에서 공유 가능한 상태 객체
주입 방식 @StateObject, @ObservedObject로 직접 주입 상위 뷰에서 .environmentObject(_:)로 한 번 주입 후 하위 뷰 어디서나 접근 가능
수명 주기 관리 @StateObject가 수명 관리 담당 (init 한 번만 실행) 수명은 상위 뷰에서 관리되며, 하위 뷰는 참조만 함
뷰 간 데이터 공유 한정적 — 뷰 간 전달 시 수동으로 넘겨줘야 함 전역적 — 한 번 환경에 올리면 하위 뷰 어디서든 접근 가능
에러 발생 시점 옵저버가 없으면 문제 없음 .environmentObject() 주입 안 했는데 @EnvironmentObject로 접근하면 런타임 크래시 발생 ⚠️
사용 예시 특정 뷰 전용 상태 관리 전역 상태, 세션 정보, 사용자 설정 등 앱 전체 공유 상태 관리

 

 

PreferenceKey

  • 하나의 뷰에 여러 하위항목(children)이 있는 경우 자동적으로 상위 항목에서 볼 수 있는 단일 값으로 결합
  • key - value로 구성된 데이터 전달 수단
  • 전달 방향은 하위뷰 -> 상위뷰.

 

뷰에서 생성되는 이름이 있는(네임드 된)값 입니다.

 

 

 

 

전반적인 흐름 (다른 사람이 작성한 이미지 참고)

 

 

 

 

 

(참고)

https://www.hohyeonmoon.com/blog/swiftui-data-flow/

 

SwiftUI의 데이터 흐름 | Hohyeon Moon

Hohyeon Moon iOS developer. Code for a happier life. Resume

www.hohyeonmoon.com

https://velog.io/@budlebee/SwiftUI-ObservableObject

 

SwiftUI : ObservableObject 와 EnvironmentObject

여러 View 에서 사용될 State 를 한데 저장해 놓고 쓸 수 있는 기능.매 초마다 숫자가 1씩 늘어나는 어플리케이션을 만들어보자. 어플리케이션에는 두개의 View가 있고(ContentView, SecondView), 두개의 View

velog.io

https://velog.io/@kipsong/SwiftUI-Preference-Key-coordinatespace

 

[SwiftUI] Preference Key & coordinatespace

Preferencekey - value 로 구성된 데이터 전달 수단입니다.전달 방향은 하위뷰 -> 상위뷰 입니다.구현방법reduce 메소드 우리가 선언한 “PreferenceKey” 를 사용하는 하위뷰를 순회합니다. 동시에, 상위뷰

velog.io

https://protocorn93.github.io/tags/PreferenceKey/

 

Best Website Builder Online | Yoursite

Start Your Site & Connect With The World. Create your website and go online now! All plans include custom mailboxes and web hosting.

yoursite.com

 

'apple > SwiftUI, Combine' 카테고리의 다른 글

Combine 빠르게 공부 정리  (0) 2022.06.03
SwiftUI @StateObject  (0) 2022.06.02
iOS 화면 캡쳐 및 녹화 감지 (feat. SwiftUI)  (0) 2022.06.02
SwiftUI ViewModifier  (0) 2022.06.02
[SwiftUI] LazyVGrid  (0) 2022.05.25