본문 바로가기

iOS

SwiftUI + TCA 튜토리얼 4 - Scope

하나의 스토어로 메인,서브 스토어 간 State, Action 을 연결지을 수 있는 Scope 에 대해 학습합니다.

 

우선 CounterFeature 를 CounterReducer 라는 이름으로 변경해서 사용했으며,

메인 탭에서 사용할 CounterAppReducer 를 생성합니다.

 

TabView 를 통해 각 Reducer 를 분리해서 사용할 수 있게끔 하려고 합니다.

State 구조체에는 각각의 CounterReducer 의 State 를 인스턴스 프로퍼티로 갖고,

Action 열거형도 마찬가지로 각각의 CounterReducer 의 Action 을 케이스로 갖습니다.

 

자, 그럼 CounterAppReducer 의 body 프로퍼티에는 Scope 를 사용합니다.

 

스코프는 KeyPath 를 사용하여 각 State, Action 을 메인 인스턴스를 통해 내부의 프로퍼티에 접근할 수 있게끔 합니다.

그래서 각각의 만들어둔 프로퍼티를 통해 상태와 액션을 연결하고, 각각의 CounterReducer 를 초기화하여

각각의 TabBarItem 에서 Reducer 가 중복되어 연결되지 않게끔 할 수 있습니다.

 


 

근데 state 에서 var 를 사용하는 이유가 뭘까요?

 

오류 메시지

"Key path value type 'KeyPath<CounterAppReducer.State, CounterReducer.State>' cannot be converted to contextual type 'WritableKeyPath<CounterAppReducer.State, CounterReducer.State>'" 는 불변성을 가진 KeyPath를 가변성을 요구하는 컨텍스트에서 사용하려고 시도했을 때 발생합니다.


ComposableArchitecture에서 Scope를 사용할 때, 상태(state)와 작업(action)에 대한 키 경로(key path)는 수정 가능해야 합니다. 

즉, WritableKeyPath 타입이어야 합니다. 하지만 여기서 State 구조체 내의 tab1과 tab2 프로퍼티가 let으로 선언되어 있어 불변(immutable)입니다. 이것이 문제의 원인입니다.

문제를 해결하기 위해서는 tab1과 tab2 프로퍼티를 var로 선언하여 가변(mutable)으로 만들어야 합니다. 
이렇게 하면 이 프로퍼티들에 대한 WritableKeyPath를 사용할 수 있게 되어, 오류가 해결됩니다.


 

이번에는 TabView 를 생성하고, 각 탭에 생성해놓은 CounterView 를 넣습니다.

 

여기서 각 뷰에 store 만 넣는 것이 아니라, store.scope(state:action:) 을 사용해서 초기화하는 것이 보입니다.

 

이것은 CounterAPPReducer 에 만들어둔 state, action 과 연결해서 사용하겠다는 의미입니다.

 

근데 이 부분을 할 때, 뭔가 이상합니다.

body 에서 만들어 둔 스코프를 명시해서 사용하겠다 이런 느낌이 아니라, 키패스를 통해 양쪽에서 참조하는 느낌입니다.

 

그래서 실제로, 컴파일 시점에서 스코프를 주석처리하고 실행해도 문제가 없습니다.

 

하지만, 실행은 되는데 상태와 액션을 body 에서 처리해주고 있지 못하기 때문에 어떤 액션도 동작하지 않으니 스코프의 연결 상태를 잘 확인해주세요.

그리고, 이게 부모 자식간의 연결이 어떤 순서로 이루어지나 찾아봤더니 이러하다고 합니다.

 

그럼 최종적으로 App 에서 스토어를 생성하고 실행하면 끝입니다.

 

 

결과