[SwiftUI]@Published vs @State

아토시스·2023년 9월 15일
0

SwiftUI

목록 보기
1/2

@Published

@Published로 표시된 프로퍼티 값이 바뀌면 외부로 바뀐 값을 publish 하는 타입이라고 정의되어 있다.
@Published 속성으로 프로퍼티를 게시하면 , 그 타입에 대한 퍼블리셔가 생성된다
그 퍼블리셔에 접근하려면 $ 사인을 붙여야 하고 , 공식 문서 예제는 아래와 같다.

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

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

@Published로 선언된 프로퍼티 값이 변경되면, 그 프로퍼티의 WillSet 블럭에서 퍼블리싱이 발생한다.
-> 그 프로퍼티를 구독하고 잇는 구독자들이 실제로 그 프로퍼티에 새로운 값이 세팅됙 ㅣ전에 새로운 값을 전달 받게 된다는 뜻인거 같다.

위에 코드에서 sink() 는 컴바인 메서드인데 @Published로 선언된 temperature 값이 바뀔 때마다 호출된다고 한다

그래서 맨 처음에 Weather가 초기화되면서 temperature가 20으로 세팅되었을 때 20이 프린트되고, 두번째로 weather.temperature를 25로 바꿨을 때 또 25가 프린트 된다.

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

그리고 중요한 것은,

The @Published attribute is class constrained. Use it with properties of classes, not with non-class types like structures.

-> @Published는 클래스 프로퍼티에서만 사용할 수 있고, structure 같은 non-class 타입에서는 사용이 제한된다고 한다.

프로젝트를 진행할 때는 ViewModel이 observing하고 , 값이 바뀜에 따라 뷰가 다시 그려지도록 하기 위해서 viewModel을 다 @ObservableObject를 채택하도록 해주어야할 거 같다.
그런데 이 @ObservableObject는 AnyObject를 채택하는 프로토콜이기 때문에 viewModel을 class로 선언해주고, 내부 프로퍼티들을 @Published로 선언해줘야겠다.


@State

@State는 SwiftUI에 의해 관리되는 값을 읽거나 쓸 수 있는 property wrapper 타입이다.

SwiftUI는 @State 로 선언된 프로퍼티의 저장소를 관리한다고 한다.(저장소 역할 인 것은 react랑 비슷한거 같다.)
그 프로퍼티의 값이 바뀌면, SwiftUI는 view hierarchy에서 그 프로퍼티 값에 의존하는 뷰들을 업데이트한다.

그리고

Use state as th single source of truth for a given value stored in a view hirearchy.

라고 되어있는데 , single source of truth뷰에서 사용하는 데이터는 하나의 원천을 갖는다 또는 데이터가 여러 곳에서 존재하지 않고 오직 한 곳에만 존재한다 라는 뜻으로 볼 수 있다고 한다.

그래서 view hierarchy 중 여러 view에서 state로 선언된 프로퍼티들이 쓰이더라도 , 데이터의 원천은 하나라고 생각하면 될 거 같다.

만약 부모 view가 자식view에게 state 프로퍼티를 전달하면, SwiftUI는 부모뷰의 state 프로퍼티 값이 바뀔 때마다 자식view를 업데이트하겠지만 , 자식 view는 그 값을 수정할 수는 없다.
자식 view가 값을 변경하도록 하기 위해서는 , Binding을 전달해줘야한다.

struct PlayerView: View {
	@State private var isPlaying: Bool = false
    
    var body: some View {
    	PlayButton(isPlaying: $isPlaying) // 자식뷰에게 binding 전달
    }
}

struct PlayButton: View {
	@Binding var isPlaying: Bool // 부모뷰에게서 binding 값 받음
    
    var body: some View {
    	Button(isPlaying ? "Pause" : "Play") {
        	isPlaying.toggle() // 부모뷰에게서 받은 값 수정 가능
        }
    }
}

그런데 위 상황에서 자식 view가 초기화 될 때 isPlaying 도 초기화 해버리면 SwiftUI의 storage management에 충돌이 일어난다(다른 view더라도 isPlaying 은 같은 값을 써야하기 때문인 듯)

그래서 부모 view가 직접 자식view에게 state 프로퍼티를 공유하는 경우 이외에 다른 view에서 state 값에 접근하지 못하도록 state는 항상 private으로 선언하고, 그 값에 접근해야하는 view들 중에 가장 상위의 view에 위치 시켜야한다.

그리고 그 값에 접근해야 하는 자식 view들은 그 값을 읽기 전용(binding X) 또는 읽기쓰기 전용(binding O)으로 공유 받아서 사용해야 한다.
그러면 state 프로퍼티들은 어떤 thread에서도 안전하게 값을 바꿀 수 있다고 한다.

우선 @Published는 class에서만 사용되고, @State는 struct 내의 프로퍼티의 값을 바꿀 때 사용한다 (그리고 @State는 private으로 선언해야함)
그리고 @Published는 class에서만 사용되기 때문에 struct 타입인 뷰에서는 사용하기 어려울 것 같고, @State는 SwiftUI에 정의되어 있기 때문에 뷰에서 사용하는 것이 적절해보인다!!!!!!

profile
오늘보다 더 나은 내일이 되길 바라며

0개의 댓글