J
[SwiftUI] SwiftUI + TCA: ์ต์ ๋ฒ์ TCA์ ๋์ ๊ณผ์ (์์ ํฌํจ) ๋ณธ๋ฌธ
[SwiftUI] SwiftUI + TCA: ์ต์ ๋ฒ์ TCA์ ๋์ ๊ณผ์ (์์ ํฌํจ)
yujaehui 2024. 7. 5. 16:50๐ง ์์ํ๊ธฐ ์ ์…
๊ธฐ์กด ๊ธ์์ TCA๋ฅผ ์ด๋ฃจ๋ ๊ธฐ๋ณธ ์์(?)์ ๋ํด ์์๋ณด๊ณ ์๋๋ฐ์.
์ด๋ฒ ๊ธ์์๋ TCA์ ํต์ฌ ๊ฐ๋ ๊ณผ ์ต์ ๋ฒ์ ์ ์์ ๋ฅผ ๋ค๋ค๋ณด๊ฒ ์ต๋๋ค.
ํ์ตํ๋ฉด์ ๊ฐ์ฅ ํฌ๊ฒ ๋๋ ์ ์, ๋ง์ ๋ธ๋ก๊ทธ ๊ธ๊ณผ ChatGPT ๋ต๋ณ์ด ๊ณผ๊ฑฐ ๋ฒ์ ์ ๊ธฐ์ค์ผ๋ก ํ๊ณ ์์ด ์ต์ ๋ฒ์ ์ ์ค์นํ์ ๋ ์ค๋ฅ๊ฐ…
์ด์ ํ์ต ๊ณผ์ ์ด ํจ์ฌ ์ด๋ ค์ ๋๋ฐ, ์ด๋ฒ ๊ธ์ ํตํด ์๋กญ๊ฒ ํ์ตํ๋ ๋ถ๋ค์ด ๊ฐ์ ์ด๋ ค์์ ๊ฒช์ง ์๋๋ก ์ต์ ๋ฒ์ ์ ๊ธฐ์ค์ผ๋ก ์ ๋ฆฌํด๋ณด์์ต๋๋ค.
๐ TCA๊ฐ ํด๊ฒฐํด์ฃผ๋ ํต์ฌ ๊ฐ๋ ๋ค
(1) ์ํ ๊ด๋ฆฌ ๐
์ ํ๋ฆฌ์ผ์ด์ ์์ ๋ฐ์ดํฐ๋ฅผ ํจ์จ์ ์ผ๋ก ๊ด๋ฆฌํ๋ ๋ฐฉ๋ฒ์ ์ ๊ณต.
- ์ํ๋ฅผ ๋จ์ํ ๊ฐ ํ์ (struct)์ผ๋ก ์ ์ํ๊ณ ๊ด๋ฆฌ.
- ์ฌ๋ฌ ํ๋ฉด์์ ๋์ผํ ์ํ๋ฅผ ๊ณต์ ํ ์ ์๊ณ , ํ ๊ณณ์์ ๋ณ๊ฒฝ๋ ์ํ๋ฅผ ๋ค๋ฅธ ๊ณณ์์๋ ์ฆ์ ๋ฐ์ ๊ฐ๋ฅ.
์๋ฅผ ๋ค์ด, ๋ก๊ทธ์ธํ ์ ์ ์ ๋ณด๋ฅผ ์ฌ๋ฌ ํ๋ฉด์์ ๊ณต์ ํด์ผ ํ ๋, ํ๋์ State์์ ๊ด๋ฆฌํ๊ณ ๋ชจ๋ ํ๋ฉด์ด ๊ฐ์ ์ ๋ณด๋ฅผ ์ฌ์ฉํ ์ ์์.
(2) ๊ตฌ์ฑ ์์ ๋ถ๋ฆฌ(Composition) ๐งฉ
์ ํ๋ฆฌ์ผ์ด์ ์ด ์ปค์ง๋ฉด ์ ์ง๋ณด์๊ฐ ์ด๋ ค์์ง๋๋ฐ, TCA๋ ๊ธฐ๋ฅ์ ์์ ๋จ์๋ก ๋๋๊ณ , ๋์ค์ ๋ค์ ํฉ์น๋ ๊ตฌ์กฐ๋ฅผ ์ง์.
- ํ๋์ ํฐ ๊ธฐ๋ฅ์ ์ฌ๋ฌ ๊ฐ์ ์์ ๊ธฐ๋ฅ์ผ๋ก ์ชผ๊ฐค ์ ์์.
- ๋๋ ์ง ์์ ๊ธฐ๋ฅ๋ค์ ๋ ๋ฆฝ์ ์ธ ๋ชจ๋๋ก ๋ง๋ค ์ ์๊ณ , ํ์ํ ๋ ์ฝ๊ฒ ์กฐํฉ ๊ฐ๋ฅ.
์๋ฅผ ๋ค์ด, Todo ๋ฆฌ์คํธ๋ฅผ ๋ง๋ ๋ค๊ณ ํ๋ฉด ๊ฐ๋ณ Todo๋ฅผ ๋ด๋นํ๋ ๋ชจ๋, Todo ๋ฆฌ์คํธ ์ ์ฒด๋ฅผ ๋ด๋นํ๋ ๋ชจ๋, ๋ฆฌ์คํธ๋ฅผ ํํฐ๋งํ๋ ๋ชจ๋๋ก ๋๋ ๋ง๋ค๊ณ , ํ์ํ ๋ ํฉ์ณ์ ์ฌ์ฉํ ์ ์์.
(3) ์ฌ์ด๋ ์ดํํธ ๊ด๋ฆฌ ๐
๋คํธ์ํฌ ์์ฒญ, ๋ฐ์ดํฐ ์ ์ฅ, ์๋ฆผ ์ ์ก ๊ฐ์ ์ธ๋ถ ์์ ์ ํจ์จ์ ์ผ๋ก ์ฒ๋ฆฌ.
- ์ฌ์ด๋ ์ดํํธ๋ Reducer(๋ฆฌ๋์) ๋ด๋ถ์์ Effect๋ผ๋ ๊ฐ๋ ์ผ๋ก ๊ด๋ฆฌ.
- Effect๋ ํน์ ์ก์ ์ด ๋ฐ์ํ์ ๋ ์คํ๋๊ณ , ์ดํ ์๋ก์ด ์ก์ ์ ๋ฐฉ์ถํ ์๋ ์์.
- ์ด ๊ณผ์ ์ด ๋ช ํํ๊ฒ ๋ถ๋ฆฌ๋์ด ์์ด์, ์ด๋ค ์ฝ๋๊ฐ ์ด๋ค ์์ ์ ํ๋์ง ์ฝ๊ฒ ์ดํดํ ์ ์์.
์๋ฅผ ๋ค์ด, ๋ฒํผ ํด๋ฆญ ์ API๋ฅผ ํธ์ถํ๊ณ ํ๋ฉด์ ์ ๋ฐ์ดํธํ๋ค๋ ์ง, ์๋ก์ด ์ผ์ ์ถ๊ฐ ์ ์๋ฆผ์ ์์ฝํ๋ค๋ ์ง ์ด๋ฐ ๊ฒ๋ค์ด ๊น๋ํ๊ฒ ์ฒ๋ฆฌ๋ ์ ์์.
(4) ํ ์คํธ ๊ฐ๋ฅ์ฑ โ
TCA์ ํต์ฌ ์ฅ์ ์ค ํ๋๋ ํ ์คํธ ์ฉ์ด.
- ์ํ ๋ณํ์ ์ฌ์ด๋ ์ดํํธ๊ฐ ์์ธก ๊ฐ๋ฅํ๊ฒ ๋์ํ๋ฏ๋ก, ๊ฐ ๊ธฐ๋ฅ์ ๋จ์ ํ ์คํธ(Unit Test) ๊ฐ๋ฅ.
- ์ฌ๋ฌ ๊ฐ์ ๊ธฐ๋ฅ์ด ํฉ์ณ์ง ์ํ์์๋ ํ ์คํธ ๊ฐ๋ฅ.
- ์ค์ ๋คํธ์ํฌ ์์ฒญ ์์ด ๋ชจ์ ๋ฐ์ดํฐ(Mock) ๋ฅผ ํ์ฉํด API ์ฐ๋ ํ ์คํธ๊ฐ ๊ฐ๋ฅ.
์๋ฅผ ๋ค์ด, ํน์ ์ก์ ์ ๋ณด๋์ ๋ ์ํ๊ฐ ์ฌ๋ฐ๋ฅด๊ฒ ๋ฐ๋๋์ง, API ์์ฒญ์ด ๋๋ ํ ์ฌ๋ฐ๋ฅธ ๋ฐ์ดํฐ๊ฐ ํ๋ฉด์ ํ์๋๋์ง ํ์ธํ ์ ์์.
(5) ์ฌ์ฉํ๊ธฐ ํธ๋ฆฌํ API ์ ๊ณต ๐ฏ
TCA๋ ์์ ๊ธฐ๋ฅ๋ค์ ์ฝ๊ณ ์ง๊ด์ ์ผ๋ก ์ฌ์ฉํ ์ ์๋๋ก ์ผ๊ด๋ ๊ตฌ์กฐ๋ฅผ ์ ๊ณต.
- State, Reducer, Effect, Store ๊ฐ์ ๊ฐ๋ ์ ์ดํดํ๋ฉด ์ ํ๋ฆฌ์ผ์ด์ ์ ๋ฐ์์ ๊ฐ์ ๋ฐฉ์์ผ๋ก ๊ฐ๋ฐ ๊ฐ๋ฅ.
- ์๋ก์ด ๊ธฐ๋ฅ์ ์ถ๊ฐํ ๋ ๊ธฐ์กด ๊ตฌ์กฐ๋ฅผ ๊ทธ๋๋ก ์ ์งํ๋ฉด์ ํ์ฅ ๊ฐ๋ฅํด์ ์ ์ง๋ณด์ ์ฉ์ด.
๐ TCA์ ํต์ฌ ๊ตฌ์ฑ ์์
๊ตฌ์ฑ ์์ | ์ค๋ช |
State | ์ ํ๋ฆฌ์ผ์ด์ ์ ํ์ฌ ์ํ๋ฅผ ๋ด๊ณ ์๋ ๊ตฌ์กฐ์ฒด |
Action | ์ฌ์ฉ์์ ์ ๋ ฅ ๋๋ ๋ด๋ถ ์ด๋ฒคํธ๋ฅผ ๋ํ๋ด๋ ์ด๊ฑฐํ |
Reducer | Action์ ๋ฐ์ State๋ฅผ ๋ณ๊ฒฝํ๋ ์์ ํจ์ |
Effect | ๋คํธ์ํฌ ์์ฒญ, ํ์ด๋จธ ๋ฑ ๋น๋๊ธฐ ์์ ์ ์ฒ๋ฆฌํ๋ ํจ์ |
Store | State, Reducer, Effect๋ฅผ ์กฐํฉํ์ฌ ์ํ๋ฅผ ๊ด๋ฆฌํ๋ ์ค์ ์ ์ฅ์ |
๐ TCA์ ๋์ ๊ณผ์ (๋ฐ์ดํฐ ํ๋ฆ)
TCA๋ ๋จ๋ฐฉํฅ ๋ฐ์ดํฐ ํ๋ฆ(Unidirectional Data Flow)์ ๋ฐ๋ฆ ๋๋ค.
[์ฌ์ฉ์ ์
๋ ฅ] → [Action ๋ฐ์] → [Reducer ์คํ] → [State ๋ณ๊ฒฝ] → [View ์
๋ฐ์ดํธ]
- ์ฌ์ฉ์๊ฐ ๋ฒํผ์ ๋๋ฆ (View → Action)
- Action์ด Store๋ก ์ ๋ฌ๋จ (Store.send(action))
- Reducer๊ฐ Action์ ๋ฐ์ State๋ฅผ ๋ณ๊ฒฝํจ
- Store๊ฐ State์ ๋ณ๊ฒฝ์ ๊ฐ์งํ๊ณ View๋ฅผ ๋ค์ ๊ทธ๋ฆผ
โก TCA์์๋ State๋ฅผ ์ง์ ๋ณ๊ฒฝํ ์ ์๊ณ , ๋ฐ๋์ Reducer๋ฅผ ํตํด ๋ณ๊ฒฝํด์ผ ํจ!
๐ TCA์ ์ฝ๋ ์์ (Counter ์ฑ)
(1) State (์ฑ์ ์ํ)
import ComposableArchitecture
struct CounterFeature: Reducer {
struct State: Equatable {
var count: Int = 0
}
}
- State๋ ์ฑ์ ์ํ๋ฅผ ๋ํ๋ด๋ ๊ตฌ์กฐ์ฒด
- Equatable์ ์ค์ํ์ฌ ์ํ ๋ณํ๋ฅผ ๊ฐ์ง ๊ฐ๋ฅ
(2) Action (์ฌ์ฉ์์ ์ ๋ ฅ)
enum Action: Equatable {
case increment
case decrement
}
- Action์ ์ํ ๋ณ๊ฒฝ์ ํธ๋ฆฌ๊ฑฐํ๋ ์ด๊ฑฐํ
- increment, decrement ๊ฐ์ ์ด๋ฒคํธ๋ฅผ ์ ์
(3) Reducer (์ํ ๋ณ๊ฒฝ ๋ก์ง)
var body: some ReducerOf<Self> {
Reduce { state, action in
switch action {
case .increment:
state.count += 1 // ์ํ ๋ณ๊ฒฝ
return .none
case .decrement:
state.count -= 1 // ์ํ ๋ณ๊ฒฝ
return .none
}
}
}
- Reducer๋ ์์ ํจ์(Pure Function) ์ด๋ฉฐ, State๋ฅผ ๋ณ๊ฒฝํ๋ ์ ์ผํ ๊ณณ
- return .none์ ๋ถ์ํจ๊ณผ(Side Effect)๊ฐ ์์์ ์๋ฏธ
(4) Store (์ค์ ์ํ ๊ด๋ฆฌ)
import SwiftUI
struct CounterView: View {
let store: StoreOf<CounterFeature>
var body: some View {
WithViewStore(store, observe: { $0 }) { viewStore in
VStack {
Text("\\(viewStore.count)")
HStack {
Button("-") { viewStore.send(.decrement) }
Button("+") { viewStore.send(.increment) }
}
}
}
}
}
- Store๋ ์ํ(State)๋ฅผ ์ ์ฅํ๊ณ , Reducer๋ฅผ ์คํํ๋ ์ค์ ์ ์ฅ์ ์ญํ
- viewStore.send(.increment) → Action์ Reducer๋ก ์ ๋ฌํ์ฌ State๋ฅผ ๋ณ๊ฒฝ
(5) ์ฑ ์คํ ์ Store ์ฃผ์ (์ฑ์ ์ง์ ์ )
import SwiftUI
import ComposableArchitecture
@main
struct TCAExampleApp: App {
var body: some Scene {
WindowGroup {
CounterView(
store: Store(
initialState: CounterFeature.State(), // ์ด๊ธฐ ์ํ
reducer: { CounterFeature() } // Reducer ์ฃผ์
)
)
}
}
}
- ์ฑ์ด ์คํ๋ ๋ Store๋ฅผ ์์ฑํ์ฌ CounterView์ ์ฃผ์
๐ฅ ์ ์ฒด ์ฝ๋
import SwiftUI
import ComposableArchitecture
// MARK: - Feature ์ ์ (Reducer)
struct CounterFeature: Reducer {
struct State: Equatable {
var count = 0 // ํ์ฌ ์นด์ดํธ ๊ฐ(count)์ ์ ์ฅ
}
enum Action: Equatable {
case increment // ์ซ์ ์ฆ๊ฐ
case decrement // ์ซ์ ๊ฐ์
}
var body: some ReducerOf<Self> {
Reduce { state, action in
switch action {
case .increment:
state.count += 1
return .none
case .decrement:
state.count -= 1
return .none
}
}
}
}
import SwiftUI
import ComposableArchitecture
// MARK: - SwiftUI View
struct CounterView: View {
let store: StoreOf<CounterFeature>
var body: some View {
WithViewStore(self.store, observe: { $0 }) { viewStore in
VStack(spacing: 20) {
Text("์นด์ดํธ: \\(viewStore.count)")
.font(.largeTitle)
HStack(spacing: 40) {
Button(action: { viewStore.send(.decrement) }) {
Text("–")
.font(.largeTitle)
.frame(width: 60, height: 60)
.background(Color.red.opacity(0.7))
.foregroundColor(.white)
.clipShape(Circle())
}
Button(action: { viewStore.send(.increment) }) {
Text("+")
.font(.largeTitle)
.frame(width: 60, height: 60)
.background(Color.green.opacity(0.7))
.foregroundColor(.white)
.clipShape(Circle())
}
}
}
.padding()
}
}
}
import SwiftUI
import ComposableArchitecture
@main
struct TCAExampleApp: App {
var body: some Scene {
WindowGroup {
CounterView(
store: Store(
initialState: CounterFeature.State(), // ์ด๊ธฐ ์ํ
reducer: { CounterFeature() } // Reducer ์ฃผ์
)
)
}
}
}
๐ง ๋ง์น๋ฉฐ…
๋ค์์๋ ์ด๋ฒ ๊ธ์์๋ ์ค๋ช ํ์ง ์์ Effect์ ๋ํด ๋ค๋ค๋ณด๋๋ก ํ๊ฒ ์ต๋๋ค.
์ถ๊ฐ์ ์ผ๋ก DependencyValues์ API ํต์ ์์ ๋ ํฌํจํด์ ์์ฑํ ์์ ์ ๋๋ค.
'SwiftUI' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
[SwiftUI] SwiftUI + TCA: Effect? DependencyValues? (์์ ํฌํจ) (0) | 2024.07.08 |
---|---|
[SwiftUI] SwiftUI + TCA: Client ํ๊ณ ๊ฐ๊ธฐโฆ (0) | 2024.07.05 |
[SwiftUI] SwiftUI + TCA : Redux ํจํด ์ํฅ? ๋จ๋ฐฉํฅ? (0) | 2024.06.30 |
[SwiftUI] @AppStorage (0) | 2024.05.23 |
[SwiftUI] @ObservableObject / @Published / @StateObject / @ObservedObject (0) | 2024.05.20 |