TCA(The Composable Architecture) 1.0을 정리해보자.
(글을 쓰고 있는 지금 현재 TCA 버전 1.9.1이 최신 버전이라는 것은 비밀..😱)

일단 TCA는 선언형 UI인 SwiftUI와 잘 맞는 아키텍처로 알려져있고, 또 단방향 Flow라는 특징을 가지고 있다.
간략하게 설명하면 View에서 어떤 action이 들어오면 TCA Store 내부 Action에서 State를 변경 시켜주고,
그 변경된 State를 SwiftUI View에서 감지하여 화면이 업데이트 되는 방식이다.

(대충 위의 그림과 같은 데이터의 방향성을 가진다. (출처: https://www.merowing.info/multi-store-tca/))
기존 1.0 버전 이전에서는 State, Action, Reducer 구조체를 각각 따로 만들었는데,
1.0부터 Reducer Protocol을 준수하는 방법으로 개선되었다.
Reducer Protocol에는 State 구조체, Action 열거형, Reducer function이 포함되어 있고,
각 내용을 작성해주기만 하면 된다.
import ComposableArchitecture
struct SomethingFeature: Reducer {
struct State: Equatable {
var name: String = ""
var age: Int = 0
}
enum Action: Equatable {
case nameButtonTapped
case ageButtonTapped
}
var body: some ReducerOf<Self> {
Reduce { state, action in
switch action {
case .nameButtonTapped:
state.name = "hello Mr.L"
return .none
case .ageButtonTapped:
state.age += 1
return .none
}
}
}
}
struct SomethingView {
...
}
주로 위의 코드와 같이 Reducer를 채택한 struct 하나와
SwiftUI View를 쌍으로 개발하게 된다. (ex, SomethingFeature, SomethingView)
Reducer body안에서 return .none은 Effect가 따로 없다는 뜻인데,
Effect로는 타이머, API 호출, 다른 Action 호출 등 여러가지를 사용할 수 있다.
위의 예제는 간단하게 나타낸 것으로, 따로 Effect를 만들지는 않았다.
import SwiftUI
import ComposableArchitecture
struct SomethingView: View {
let store: StoreOf<SomethingFeature>
var body: some View {
WithViewStore(self.store, observe: { $0 }) { viewStore in
VStack {
HStack {
Text("\(viewStore.name)")
Button("Name Button") {
viewStore.send(.nameButtonTapped)
}
}
HStack {
Text("\(viewStore.age)")
Button("Age Button") {
viewStore.send(.ageButtonTapped)
}
}
}
}
}
}
View에서는 위와 같이 화면을 구성해보았는데,
let store: StoreOf<SomethingFeature>를 선언하고, store를 바로 접근하여 쓰는 것이 아니라
WithViewStore을 두어서 viewStore로 접근해야한다.
(그렇지 않으면 매우 느린 동작을 볼 수 있다고 한다..)
그러나 iOS 17이상이라면 store에 직접 접근 가능하다고 한다.
SwiftUI가 내부적으로 어떤 상태인지 정확하게 파악 가능하기 때문이라고 하는데 이건 나중에 직접 살펴봐야될 것 같다.
추가적으로, #Preview에서
위에 만든 Store를 초기화해줄 때 ._printChanges()를 해주면 버튼을 눌러 동작할 때마다 상태값이 어떻게 바뀌는지
State가 콘솔에 찍혀서 볼 수 있다.
#Preview {
SomethingView(
store: Store(initialState: SomethingFeature.State()) {
SomethingFeature()
._printChanges()
}
)
}
- 버튼 눌렀을 때 보이는 콘솔
received action:
SomethingFeature.Action.nameButtonTapped
SomethingFeature.State(
- name: "",
+ name: "hello Mr.L",
age: 0
)
received action:
SomethingFeature.Action.ageButtonTapped
SomethingFeature.State(
name: "hello Mr.L",
- age: 0
+ age: 1
)
received action:
SomethingFeature.Action.ageButtonTapped
SomethingFeature.State(
name: "hello Mr.L",
- age: 1
+ age: 2
)
- 시뮬레이터 실행

Uploaded by N2T