Swift UI - Data Flow (2)

Beaver's Knowledge Storage·2022년 10월 26일
1

스윗한 SwiftUI

목록 보기
3/4
post-thumbnail

SwiftUI에서는 데이터를 다루는데 여러 프로퍼티들이 사용된다.

1. @State와 @Binding

  • @State는 상태 프로퍼티이자 원천자료이고 데이터에 대한 상태를 저장하고 관찰하는 역할을 수행한다.
  • @Binding은 연산 프로퍼티이자 파생자료이고 직접 값을 보유하는 대신, 값을 읽고 수정하여 다른 뷰에 갱신된 데이터를 전달하는 역할을 한다.
struct Chapter5ContentView: View {
    @State private var isFavorite: Bool = true
    @State private var count     : Int  = 0
    
    var body: some View {
        VStack(spacing: 30) {
            Toggle(isOn: $isFavorite) {
                Text("isFavorite : \(isFavorite.description)")
            }
            Stepper("Count : \(count)", value: $count)
        }
        .padding()
    }
}

위 예제를 한번 보자.

  • @State는 뷰 자신의 UI 상태를 저장하기 위한 데이터로 설계되었으므로 명시적으로 나타내기 위해 Private 접근제한자를 사용하는게 좋다.
  • $(달러) 접두어와 함께 @State 프로퍼티를 사용하면 내부적으로 projectedValue 프로퍼티를 사용해서 Binding 타입을 반환해주기 때문에 Toggle, Stepper의 매개변수에 사용 할 수 있다.

상태 프로퍼티를 정의한다는 것은 '데이터가 변경되면 뷰 역시 변경되어야 한다.' 라는 뜻이므로 변경 사항이 감지될 때마다 뷰를 재생성 해줘야한다.


2. ObservableObject와 @ObservedObject

  • 이 도구들은 외부의 뷰 모델이 가진 원천 자료를 다루기 위한 도구들이다.
  • ObservableObject는 프로토콜이며 AnyObject를 채택하고 있어서 struct나 enum 타입에는 사용 불가하고 참조 타입인 class에 사용 가능하다.
  • @ObservedObject는 ObservableObject 프로토콜을 준수하는 모델에 뷰가 의존성을 가진다라는 의미이고 데이터 변화를 감지하는데 사용하는 프로퍼티이다.
class User2: ObservableObject {
    let name : String = "User Name"
    var score: Int    = 0
}

struct Chapter5ContentView: View {
    @State private var isFavorite: Bool = true
    @State private var count     : Int  = 0
    @ObservedObject var user     : User2
    
    var body: some View {
        VStack(spacing: 30) {
            Text(user.name)
            Button(action: { self.user.score += 1 }) {
                Text(user.score.description)
            }
        }
        .padding()
    }
}

struct Chapter5ContentView_Previews: PreviewProvider {
    static var previews: some View {
        Chapter5ContentView(user: User2())
    }
}

위 예제를 보면 ContentView가 User2 모델에 의존성을 지니고 있고 모델의 값을 사용하고 있어서 정상 동작 할 것 같지만 그렇지 않다. 어떠한 값이 변경되었을 때 뷰를 갱신해야하는지 알려주지 않았기 때문이다. 이러한 기능을 하는 프로퍼티가 바로 @Published 이다.


3. @Published

  • 값이 변경되었을 때 값을 참조하고 있는 뷰를 갱신하라고 알려주는 프로퍼티이다.
class User2: ObservableObject {
    let name : String = "User Name"
    @Published var score: Int = 0
}

위 예제처럼 score 변수에 @Published 프로퍼티를 선언해놓으면, User2 모델을 참조하고 score 변수를 사용하고 있는 뷰에서 값의 변경을 감지하여 뷰를 업데이트 한다.

- objectWillChange

objectWillChange는 @Published처럼 프로퍼티의 변경 시점에 즉시 알리는 것이 아니라, ObservableObject 프로토콜에 선언된 프로퍼티를 통해 원하는 시점에 사용할 수 있다.

let objectWillChange = ObjectWillChangePublisher()
var score = 0 {
	willSet { objectWillChange.send() } 
}

위 예제를 보면 @Published는 ObjectWillChangePublisher가 send 메서드를 호출하는 로직을 간소화 한 것이다. 그러므로 둘의 기능은 동일하고, 객체에 알림을 전달하는 시점을 결정할 수 있는지에 대한 차이가 있다.


4. @EnvironmentObject

@EnvironmentObject는 모델에 간접적인 의존성을 가지는 레퍼 타입이다. 반면 @ObservedObject는 직접적인 의존성을 가지는 둘의 차이점이 있다.

  • @ObservedObject는 모델에 대한 참조를 뷰에 직접 전달하고, 그 자식 뷰에 동일 모델에 대한 참조를 전달하기 위해 매번 @Binding 래퍼를 이용해서 연결해야 한다.
struct ContentView: View {
    var body: some View {
        SuperView(user: User2())
    }
}

struct SuperView: View {
    @ObservedObject var user: User2
    var body: some View { SubView(user: user) }
}

struct SubView: View {
    @ObservedObject var user: User2
    var body: some View { Text(user.name.description) }
}
  • @EnvironmentObject는 environmentObject 수식어를 이용해 부모 뷰에 환경 요소로 등록하고 @EnvironmentObject 래퍼를 이용해 의존성을 만든다.
  • environmentObject 수식어에 사용될 모델은 ObservableObject 프로토콜을 준수해야한다.
  • SuperView의 모든 자식 뷰들은 동일한 User 인스턴스에 접근하여 사용할 수 있다.
struct ContentView: View {
    var body: some View {
        SuperView().environmentObject(User2())
    }
}

struct SuperView: View {
    var body: some View { SubView() }
}

struct SubView: View {
    @EnvironmentObject var user: User2
    var body: some View { Text(user.name.description) }
}

1개의 댓글

comment-user-thumbnail
2022년 11월 2일

잘 정리된 글 감사합니다.

답글 달기