[SwiftUI] Property Wrapper (3)

DongHeon·2023년 10월 25일
0

SwiftUI

목록 보기
3/6
post-thumbnail

안녕하세요 오늘은 SwiftUI에서 사용하는 ObservableObject, Published에 대해서 알아보겠습니다.

ObservableObject & @Published

간단하게 정리하면 ObservableObjectPubliser를 가지고 있는 타입을 의미합니다.

그럼 코드를 통해 좀 더 정리해 보겠습니다.

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)
john.objectWillChange
    .sink { _ in
        print("\(john.age) will change")
}

print(john.haveBirthday())

ObservableObject 객체는 objectWillChange 를 기본적으로 사용해 @Published 속성이 변경되기 전에 값을 사용할 수 있습니다.

Published도 이전에 다뤘던 Property Wrapper처럼 ($)로 접근이 가능합니다. 아래 코드처럼 @Published는 반드시 ObservableObject 객체 내부에서만 사용하는 것은 아닙니다.

class Weather {
    @Published var temperature: Double
    init(temperature: Double) {
        self.temperature = temperature
    }
}


let weather = Weather(temperature: 20)
weather.$temperature
    .sink() {
        print ("Temperature now: \($0)")
}
weather.temperature = 25

// Prints:
// Temperature now: 20.0
// Temperature now: 25.0

날씨의 온도가 변경되면 온도의 willSet 블록에서 publishing이 발생합니다.

@Published propertyObserver Property와 마찬가지로 initializer 통해 초기화하면 클로저가 동작하지 않습니다.

Observer PropertywillSet을 이용하면 이전 값과 새로 입력받은 값을 알 수 있습니다.

예시

예시

각 Row에 있는 버튼이 눌리면 Text를 변경시키는 화면이 있습니다. 데이터의 변화를 관찰하면서 변화가 발생하면 View를 업데이트해야 합니다.

@State@Binding을 이용해도 충분히 구현할 수 있는데 왜 @PublishedObservableObject를 사용해야 하는지 궁금할 수 있습니다.

물론 공식 문서를 보면 Int, String과 같은 타입에는 State를 사용하라고 나와있습니다.

개인적인 생각으로는 앱을 개발하다면 Model 타입을 따로 만들어 관리하는 경우가 많고 무엇보다 View에서 많은 프로퍼티를 가지고 있으면 관리하기 어렵기 때문에 관련 있는 데이터를 묶어 따로 객체로 관리하기 위해 사용할 수 있다고 생각합니다.

코드를 확인해 보겠습니다.

struct Item: Identifiable {
    let id = UUID()
    var value: Int
}

class TestModel: ObservableObject {
    @Published var list = [
        Item(value: 1),
        Item(value: 2),
        Item(value: 3),
        Item(value: 4)
    ]
}

struct TestView: View {
    @StateObject var model = TestModel()

    var body: some View {
        List {
            ForEach(model.list.indices, id: \.self) { index in
                RowView(item: model.list[index]) {
                    model.list[index].value += 1
                }
            }
        }
    }
}

struct RowView: View {
    var item: Item
    var action: () -> Void

    var body: some View {
        HStack {
            Text("\(item.value)")
            Spacer()

            PlusButton {
                action()
            }
        }
    }
}

struct PlusButton: View {
    var action: () -> Void

    var body: some View {
        Button {
            action()
        } label: {
            Text("+")
                .foregroundColor(.white)
                .padding()
                .padding(.horizontal)
                .background(.blue)
                .cornerRadius(10)
        }
    }
}

SwiftUI는 Controller라는 개념이 없기 때문에 View 자체에서 발생하는 User Input을 해당 View에서 바로 처리하는 것이 맞지 않을까 생각합니다.

제 예시를 보면 결국 User Input이 발생하는 곳은 PlusButton이고 Button에서 @StateObject Property를 가지고 데이터를 변경시키는 방법을 고민했지만 찾을 수 없었습니다. 그래서 저는 action을 주입해 Button에서 해당 action을 호출하는 방식을 사용했습니다.

혹시 @StateObject Property를 주입해서 처리하는 방법이나 다른 접근 방식이 있으시다면 댓글 부탁드립니다.

오늘은 여기서 마무리하고 다음에는 @StateObject@ObserverObject의 차이에 대해 알아보겠습니다.

참고 자료

SwiftUI 공식 문서 - Published
SwiftUI 공식 문서 - ObservableObject

0개의 댓글