What is @StateObject????
우리의 G선생 왈
ObservableObject protocol을 따르는 객체의 생명주기를 관리하는데 쓰이는 프로퍼티 wrapper라고 한다.
stateobject를 지정하는 순간 스유(swiftui)가 뷰계층구조가 살아있는동안 유지되도록 보장한다.
>> 이것은 다른 화면으로 넘어갔다가 오더라도 객체는 지속된다는 뜻이다
객체를 생성하고 객체 관리,
뷰의 수명동안 새로운 인스턴스를 한번만 찍어냄. 이 찍어내는 타이밍은 뷰에서 생성자 처음 호출할때 그때 한번!!! 그때 stateObject생성자 한번찍힌다고 생각하면 된다.
그러면 어떻게 사용할까요?
1. 이런식으로 클래스 인스턴스 한번 찍어낼때
struct MyView: View {
@StateObject private var viewModel = MyViewModel()
}
2. init메서드를 통해 값을 받아서 viewModel에 값을 넣어줄수도 있다.(
struct MyView: View {
@StateObject private var viewmodel: MyViewModel
init(name: String) {
// SwiftUI ensures that the following initialization uses the
// closure only once during the lifetime of the view, so
// later changes to the view's name input have no effect.
_model = StateObject(wrappedValue: { MyViewModel(name: name) }())
}
}
3. 외부에서 StateObject를 주입해준다.
1,2에서는 당연히 그 해당뷰에서 StateObject인스턴스를 찍어내기때문에 private을 사용하지않을이유가 없다.
하지만 3번에서는... 결론만 말하자면 안된다!!!
struct DetailView: View {
@StateObject private var viewModel: DetailViewModel
init(viewModel: DetailViewModel) {
_viewModel = StateObject(wrappedValue: viewModel)
}
var body: some View {
Text(viewModel.name)
}
}
struct TestView: View {
@State private var isEnabled = false
var body: some View {
VStack {
Toggle(isOn: $isEnabled) {
Text(isEnabled ? "view is enabled" : "view is disabled")
}
DetailView(viewModel: viewModel(name: "new toggle"))
}
}
}
final class viewModel: ObservableObject {
var name: String
init(name: String) {
self.name = name
print("ViewModel init \(name)")
}
}
위의 예시를 한번보자 해당하는 DetailView를 생성하기전에 TestView에서 뷰모델을 생성하여 넘겨줬다.
결과는.....?
ViewModel init new toggle
ViewModel init new toggle
ViewModel init new toggle
ViewModel init new toggle
ViewModel init new toggle
ViewModel init new toggle
....
...
..?
실제로 내가 개발중 마주친 버그였다... 뷰모델에서 네트워크호출이 필요해 컨테이너를 생성해주고 넘겨주었을때 뷰모델 init이 무한히 찍혓다...
init(wrappedValue thunk: @autoclosure @escaping () -> ObjectType)
wrappedValue init은 위와 같이 이루어져 있다.
오잉? autoclosure가 있네? 간략히 설명하면 함수의 인자로 전달되는 코드를 감싸서 자동으로 클로저 만들어준다. 즉 클로저에서 사용되는 {}요놈 안써도 클로저라는 뜻이다. 원래는 autoclosure를 통해 한번만 생성되도록 한다.
하지만 위와 같이 인스턴스를 전달받으면 autoclosure로 인한 delay init이 작동안한다. 저 wrappedValue init에 가기전 이미 인스턴스가 생성되어버리기 때문에!!
struct DetailView: View {
@StateObject private var viewModel: DetailViewModel
init(viewModel: @autoclosure @escaping () -> DetailViewModel) {
_viewModel = StateObject(wrappedValue: viewModel())
}
}
위와 같이 따로 autoclosure를 작성해주어야한다. 겁나 복잡하죠잉? 근데 실제로 이렇게 코드를 쓰냐?ㄴㄴ난 한번도 써본적이없다.
그 이유가 사실 저기서 private를 빼주면 된다. 그러면 오토클로저고 이스케이핑이고 저리 휙휙
그렇다면 @State와는 뭐가다를까????
- @State는 현재 뷰에만 영향을 미치는 간단한 상태를 저장하는 데 사용되는 반면, @StateObject는 뷰 간에 공유할 수 있고 앱 전체의 상태에 영향을 미치는 더 복잡한 상태를 저장하는 데 사용한다고 한다!!
----------------------------------------------------------------------------------------------------------------------
@State
- Struct인 뷰에서 속성을 바꿔줄수 있는 프로퍼티 래퍼
- 근데 질문? 어? Struct에서 속성값을 바꿀땐 원래 mutating이 있었잖아?
- mutating func으로 변경된 변수는 뷰가 destroy될때 다시 초기값으로 돌아간다.
- 하지만 애플이 만들어주신 state요놈은 뷰가 destroy되고도 해당값을 재참조한다. 그러면 어떻게 이게 가능할까?
- 뷰의 일부분으로 저장되는것이 아님! SwiftUI프레임워크가 내부적으로 관리하는 특별한 저장소에 저장된다. 그래서 SwiftUI가 이러한 상태의 변경을 감지하고 해당 변경사항을 UI에 반영하여 다시 뷰를 그릴수 있는것.
- 또 질문 그러면 이 저장소는 얼마나 넓은데? ㅋㅋ
- 나와 같은 궁금증이 있는 구글링이 나왓다. 어떤사람은 85개의 State를 한뷰에 놓으면 더이상 추가가안된다한다...ㅋㅋ
- 하위뷰나 다른 뷰에서 사용하려면 @binding 붙여야함
- state property에 해당하는 변수 값이 변경되면 view 재랜더링> 항상최신값가질수있다.
- 뷰전체가 랜더링되면 비효율적이기에 하위뷰에 데이터 변동 반영되는 뷰만 따로 뺀다.
- 상태 프로퍼티와 바인딩할때는 변수앞에 $붙여주면 끝
@ObservedObject
- 외부에서 생성된 객체(즉 StateObject로 생성된것)을 받아서 뷰에서 사용할때 사용
- 외부 객체 상태변화 감지하고 변경사항을 뷰에 업뎃
'SWIFTUI' 카테고리의 다른 글
go back to basic - @main 플젝만들면 항상생기는 파일 이건 몰까? (0) | 2024.03.01 |
---|---|
some(Opaque Type)&any Keyword (1) | 2024.02.29 |
@ViewBuilder & @resultBuilder (1) | 2024.02.26 |
근본으로 돌아가자(3)-View Layout (2) | 2024.02.26 |
근본으로 돌아가자(2) - animation & iOS 17에서 바뀐것들 (0) | 2023.12.14 |