The TCA implementation demonstrates a functional architecture pattern with unidirectional data flow, emphasizing composition, testing, and predictability. It provides a structured approach to managing state and side effects through explicit actions and pure reducers.
graph TB
subgraph "View Layer"
V1[TCAContentView]
V2[TCASidebarView]
V3[TCAContentListView]
V4[TCADetailView]
V5[TCASettingsContentView]
end
subgraph "Feature Layer (Reducer)"
F1["TCAAppFeature
@Reducer"]
F2["State
@ObservableState"]
F3["Action
enum"]
F4["Reducer
body"]
end
subgraph "Store"
S1["Store
Runtime"]
end
subgraph "Model Layer"
M1["TCAListItem
struct"]
M2["TCASidebarCategory
enum"]
end
V1 -- "reads/sends" --> S1
V2 -- "reads/sends" --> S1
V3 -- "reads/sends" --> S1
V4 -- "reads" --> S1
V5 -- "reads/sends" --> S1
S1 -- "holds" --> F2
S1 -- "accepts" --> F3
S1 -- "executes" --> F4
F4 -- "transforms" --> F2
F4 -- "uses" --> M1
F4 -- "uses" --> M2
F2 -- "contains" --> M1
F2 -- "references" --> M2
style F1 fill:#e1f5ff
style F2 fill:#d4edda
style F3 fill:#fff3cd
style F4 fill:#f8d7da
style S1 fill:#e7e7ff
style M1 fill:#fff4e1
style M2 fill:#fff4e1
✅ Use TCA when:
- Building complex applications with lots of state
- Maximum testability is critical
- You want composable, reusable features
- Team embraces functional programming
- Need time-travel debugging
- Want explicit, predictable state changes
- Building for long-term maintainability
❌ Consider alternatives when:
- Building simple apps or prototypes
- Team unfamiliar with functional programming
- Can't add external dependencies
- Want minimal boilerplate
- Need rapid prototyping speed
The single source of truth for your feature:
@ObservableState
struct State: Equatable {
var selectedCategory: TCASidebarCategory? = .category1
var selectedItem: TCAListItem? = nil
var category1Items: [TCAListItem] = []
}Characteristics:
- Struct (value type) for immutability
- Equatable for change detection
- @ObservableState for SwiftUI integration
- Contains ALL data the feature needs
An enum describing every way state can change:
enum Action: Equatable {
case categorySelected(TCASidebarCategory?)
case itemSelected(TCAListItem?)
case addItemTapped
case deleteItem(TCAListItem)
}Characteristics:
- Enum cases represent events
- Associated values carry data
- Past tense naming (what happened)
- Equatable for testing
A pure function that evolves state:
var body: some Reducer<State, Action> {
Reduce { state, action in
switch action {
case let .categorySelected(category):
state.selectedCategory = category
state.selectedItem = nil
return .none
}
}
}Characteristics:
- Pure function (no side effects)
- Mutates state parameter
- Returns effects to execute
- Testable in isolation
The runtime that powers the feature:
let store = Store(initialState: TCAAppFeature.State()) {
TCAAppFeature()
}Characteristics:
- Holds current state
- Accepts action dispatches
- Runs reducer
- Executes effects
- Notifies views of changes
sequenceDiagram
actor User
participant View
participant Store
participant Reducer
participant State
User->>View: Taps "Add Item"
View->>Store: send(.addItemTapped)
Store->>Reducer: reducer(state, .addItemTapped)
Reducer->>State: Mutate state
Reducer-->>Store: Return Effect (.none)
Store-->>View: State changed notification
View-->>User: UI updates automatically
Examples/TCA/
├── Models/ # Pure data structures
│ ├── TCASidebarCategory.swift # Category enum
│ └── TCAListItem.swift # Item struct
│
├── Features/ # State + Actions + Reducers
│ └── TCAAppFeature.swift # Main feature definition
│
└── Views/ # SwiftUI views
├── TCAContentView.swift # Root view
├── TCASidebarView.swift # Sidebar (first pane)
├── TCAContentListView.swift # Content list (second pane)
├── TCADetailView.swift # Detail (third pane)
└── TCASettingsContentView.swift # Settings form
Purpose: Pure data structures
struct TCAListItem: Identifiable, Hashable, Equatable {
let id = UUID()
let title: String
let subtitle: String
// ... more properties
}Characteristics:
- Immutable (
let) - Equatable for state comparison
- No business logic
Purpose: Define feature behavior
@Reducer
struct TCAAppFeature {
@ObservableState
struct State: Equatable { /* ... */ }
enum Action: Equatable { /* ... */ }
var body: some Reducer<State, Action> {
Reduce { state, action in
// Transform state based on action
}
}
}Characteristics:
- @Reducer macro for composition
- State is observable
- Actions are equatable
- Reducer is pure function
Purpose: Display state and send actions
struct TCAContentView: View {
let store: StoreOf<TCAAppFeature>
var body: some View {
WithPerceptionTracking {
// UI that reads store.state
Button("Add") {
store.send(.addItemTapped)
}
}
}
}Characteristics:
- WithPerceptionTracking for observation
- Read state via
store.state - Send actions via
store.send() - No business logic
TCA makes testing straightforward with TestStore:
func testAddItem() async {
let store = TestStore(initialState: TCAAppFeature.State()) {
TCAAppFeature()
}
await store.send(.addItemTapped) {
$0.category1Items.append(/* expected item */)
}
}All state changes are explicit through actions:
store.send(.categorySelected(.category1))Every feature is fully testable:
let store = TestStore(/* ... */)
await store.send(.action)Features can be broken down and composed:
Scope(state: \.sidebar, action: \.sidebar) {
SidebarFeature()
}Action log shows complete history:
Action: categorySelected(.category1)
Action: itemSelected(item)
Action: addItemTapped
| Aspect | TCA | Redux | MVVM | VIPER |
|---|---|---|---|---|
| Complexity | High | Medium | Medium | High |
| Testability | Excellent | Great | Good | Excellent |
| Learning Curve | Steep | Medium | Medium | Steep |
| Boilerplate | High | Medium | Low | High |
| Effects System | Built-in | Manual | Manual | Manual |
| Composition | Excellent | Good | Good | Good |
| Dependencies | Required | Optional | None | None |
- Keep State Minimal - Only store what's necessary
- Use Equatable - Makes change detection efficient
- Pure Reducers - No side effects in reducer body
- Test Everything - TCA makes it easy
- Compose Features - Break large features down
- Document Actions - Clear names explain intent
- Use Effects - All async work through effects
TCA provides a comprehensive architecture with:
- ✅ Unidirectional data flow
- ✅ Complete testability
- ✅ Composable features
- ✅ Built-in effects system
- ✅ Time-travel debugging
- ✅ Type safety
Trade-off: More complexity and boilerplate than simpler patterns.
Recommendation: Use for complex apps where testability and predictability are critical priorities.