이전의 글에서 Store는 런타임동안 Reducer의 인스턴스를 관리하는 참조타입의 객체라고 하였다. 결국 상태, 액션에 대한 반응까지 모두 포함한 역할을 한다. 그렇다면 이 Store를 뷰에서는 어떻게 사용해야할까?
Store
let store: StoreOf<Reducer이름>
- Scope : 하위 State 및 Action을 다루는 스토어로 변환 ㄱㄴ. 2가지 인자를 받는다. 1. State: 상태 추출할 keypath 2. Action: 액션 추출할 Keypath. -> 즉 전체의 상태에서 너 필요한거만 Store로 줄께. => View는 필요한 상태와 액션만 접근할 수 있게된다. 모듈화, 유연성 굿(자연스럽게 유닛테스트도)
ViewStore
View에 필요한 상태만 구독하고 업데이트하는 역할. 하나의 스토어에서 또 많은 작업들이 이루어지다보면 이전에 했던 MVC나 MVVM처럼 관리하기 힘들어지기에 어떤 뷰에서 하위 뷰를 만들때 상위뷰의 상태 일부를 소유하는 별도의 스토어를 연결하게 했다. 하지만 자식 뷰에서 액션을 받으면 이를 다시 부모스토어에 전달하고 그 부모 스토어는 리듀서를 통해 다시 내부 상황을 업데이트 해야하기에 여러번 랜더링이 될수 있다.변화를 중복하는 것을 막기위해 ViewStore를 만들어 관리를 하였다고 생각하면 된다.
ViewStore는 ViewState(상위것들중에 필요한것들만 추출한 State) & ViewAction으로 이루어져 있다.
이 뷰 스토어를 연결하는 것은 withViewStore메서드를 사용. Store를 View builder에서 사용할수 있는 ViewStore로 변환해주는것.
하지만 주의해야할 부분이 있다고 한다. withViewStore가 감싸고 있는 뷰가 복잡해질수록 컴파일러 성능이 저하된다고 한다.
- 타입을 명시하여 컴파일러 성능을 개선한다. withViewStore(self.store, observe: {$0}) {viewStore: ViewStoreOf<Feautre> in}
- 이니셜라이저를 통해 Store를 주입받고, 그 안에서 ViewStore를 생성
let store: StoreOf<CounterFeature>
@ObservedObject var viewStore: ViewStoreOf<CounterFeature>
init(store: StoreOf<CounterFeature>) {
self.store = store self.viewStore = ViewStore(self.store, observe: { $0 })
}
또한 주의해야할 점이 Store는 클래스로 참조타입이기에 Thread-safe 하지 않다고 나와있다. Action이 전달될때 리듀서 실행될때 State변경이 동시에 일어난다면 동시성문제가 발생한다. 그래서 모든 액션은 동일한 쓰레드에서 작업을 하도록 해야한다고 나와있다.
추가!! 최신 TCA에서는 ViewStore이 딱히 필요가 없다. 왜? Store가 observableObject 프로토콜을 준수하여 상태변화를 감지할 수 있다. ObservableState를 사용하라고 한다. 1.7이상
@Reducer
struct TCAlove {
@ObservableState
struct State {}
enum Action {}
var body: some ReducerOf<Self>{}
}
struct TCAView: View {
let store: StoreOf<TCAlove>
var body: some View{
Text(store.count.descritiption)
Button("-"){store.send(.someting)}
}
}
요런식으로 iOS 17 이상, TCA 1.7이상의 버전에서는 사용가능하다. 와 편해!~
그렇다면 이제 바인딩은 어떻게 할지를 공부해보자.
쉽다. 이전에 state에서 상태를 관리하고 Action을 통해 뷰에서 어떤행위를 할지를 만들고 reducer를 통해 action에 다른 State를 변화시키면 된다고 했다. viewstore.binding(get:, send:)를 통해 get에서는 전달된 객체를 바인딩하고 send에 전달된 액션을 통해 store에서 다시 피드백을 하여 액션에 맞는 Reducer를 통해 State를 변경한다.(비즈니스 로직).
이렇게 되면 자연스럽게 State, Action이 많을수록 Reducer의 switch문도 길어지기에 TCA는 다양한 Binding을 제시한다.
다양한 바인딩 방법들
- @BindingState: State 구조체 필드에 프로퍼티래퍼로 추가하면 해당것은 Swiftui의 UI컨트롤에 바인딩이 가능하여 뷰에서 해당필드 조정할 수 있다.
- 하지만 당연히 모든 필드에 @BindingState? 이럴꺼면 왜씀? 뷰에서 직접변하게 하는거면 @Published쓰지. 그래서 최대한 진짜진짜 필요할때만 써야함. -> Swiftui 컴포넌트에 전달하기 위한 필드에만 사용을 해보자
- 하지만 최신버전에서는 @ObservableState를 이용하여 BindingState를 안씀.
- BindableAction Protocol: Action열거형에 채택함으로써 State의 모든 필드 Action을 하나의 case로 축소한다.
- 축소된 Action case는 제너릭 타입을 갖는 BindingAction을 associatedValue로
enum Action: BindableAction {
case binding(BindingAction<State>)
}
- State, Action하나씩 했으니 Reducer도 있겠지? ㅇㅇBindingReducer라는 넘이 있다. BindingAction이 들어오면 State를 업뎃해주는 Reducer. View에서 액션을 직접호출해주지 않고 Store내부의 state가 업뎃
=> View에서 액션을 호출하지 않고, 기존의 스유장점으로 바로 필드에 바인딩하여 직관적이게 사용가능
참고사이트:
https://axiomatic-fuschia-666.notion.site/Chapter-3-TCA-2-c56b24efb2154dad9ed8e54139247024
https://axiomatic-fuschia-666.notion.site/Chapter-4-TCA-Binding-87f748b3f8fa41a08a9089d78aeb422c
'SWIFTUI' 카테고리의 다른 글
TCA-Dependency...DI,DIP를 곁들인 (2) | 2024.09.20 |
---|---|
TCA-3번째시간 Dependency (0) | 2024.07.19 |
TCA(1)- (mvvm...-> NEXT?) (0) | 2024.07.04 |
go back to basic - @main 플젝만들면 항상생기는 파일 이건 몰까? (0) | 2024.03.01 |
some(Opaque Type)&any Keyword (1) | 2024.02.29 |