Contents 非表示
TCAアーキテクチャとは
The Composable Architecture(TCA)は、Point-Freeが開発したSwiftUIアプリケーション開発のためのフレームワーク。単方向データフローと関数型プログラミングの原理に基づき、Testableで保守性の高いアプリケーションを構築できるとのこと。
TCAの基本概念
TCAは4つの主要なコンポーネントで構成
State(状態)
アプリケーションの現在の状態を表現する値型。UIに表示されるすべてのデータがここに含まれる。
Action(アクション)
ユーザーの操作やシステムイベントを表現する列挙型(enum)。ボタンタップ、テキスト入力、API呼び出しの結果などがアクションとして定義される。
Reducer(リデューサー)
現在のStateとActionを受け取り、新しいStateを返す純粋関数。副作用もここで管理。
Store(ストア)
StateとActionを管理し、ViewとReducerを繋ぐ役割を担う。
メモアプリの実装
- 追加
- 削除
- TextFieldの値変更
だけのシンプルな構造のメモアプリでざっくりと学んでみる。

Reducerの定義
struct MemoReducer: Reducer {
struct State: Equatable {
var items: [String] = []
var text: String = ""
}
enum Action: Equatable {
case addButtonTapped
case deleteButtonTapped(index: Int)
case textChanged(String)
}
func reduce(into state: inout State, action: Action) -> Effect<Action> {
switch action {
case .textChanged(let newText):
state.text = newText
return .none
case .addButtonTapped:
if !state.text.isEmpty {
state.items.append(state.text)
state.text = ""
}
return .none
case .deleteButtonTapped(let index):
guard state.items.indices.contains(index) else { return .none }
state.items.remove(at: index)
return .none
}
}
}
State の設計
items
: メモリストのアイテムを格納する配列text
: テキストフィールドの現在の入力値
Action の定義
addButtonTapped
: 追加ボタンがタップされた時deleteButtonTapped(index: Int)
: 削除ボタンがタップされた時(削除するアイテムのインデックス付き)textChanged(String)
: テキストフィールドの値が変更された時
Reducer ロジック
各Actionに対する状態変更のロジックをここで実装する。TCAでは状態の変更は必ずReducer内で行い、副作用が必要な場合はEffect
を返す。今回は副作用がないため、すべて.none
を返すだけでOK。
Viewの実装
struct HomeView: View {
let store: Store<MemoReducer.State, MemoReducer.Action>
var body: some View {
WithViewStore(self.store, observe: { $0 }) { viewStore in
VStack {
List {
ForEach(Array(viewStore.items.enumerated()), id: \.offset) { index, item in
HStack {
Text("🍎 \(item)")
Spacer()
Button("削除") {
viewStore.send(.deleteButtonTapped(index: index))
}
.foregroundColor(.red)
}
}
}
HStack {
TextField("新しいアイテム", text: viewStore.binding(
get: \.text,
send: TodoReducer.Action.textChanged
))
.textFieldStyle(RoundedBorderTextFieldStyle())
Button("追加") {
viewStore.send(.addButtonTapped)
}
}
.padding()
}
}
}
}
アプリのエントリーポイントの実装はこちら
import SwiftUI
import ComposableArchitecture
@main
struct SwiftUI_PlaygroundApp: App {
// MARK: - Body
var body: some Scene {
WindowGroup {
HomeView(
store: Store(
initialState: TodoReducer.State(),
reducer: {
TodoReducer()
}
)
)
}
}
}
WithViewStore
TCAではWithViewStore
を使用してStoreから状態を観測し、Actionを送信する。observe
パラメータで観測したい状態の部分を指定する。
データバインディング
テキストフィールドのバインディングはviewStore.binding
を使用して作成。get
で現在の値を取得し、send
で値の変更をActionとして送信。
Actionの送信
ボタンタップなどのイベントはviewStore.send()
メソッドを使用してActionを送信する。
TCA vs MVVM 比較表
項目 | TCA | MVVM |
---|---|---|
学習コスト | ❌ 高い – 関数型プログラミングの知識が必要 | ✅ 低い – 一般的なパターンで理解しやすい |
状態管理 | ✅ 予測可能 – 単方向データフローで状態変更が追跡しやすい | ❌ 複雑になりがち – 双方向バインディングで状態が散在 |
テスタビリティ | ✅ 優秀 – 純粋関数でテストが容易 | ⚠️ 中程度 – ViewModelのテストは可能だが、UIとの結合度が高い |
デバッグ | ✅ 容易 – すべてのActionとState変更をトレース可能 | ❌ 困難 – 状態変更の追跡が難しい場合がある |
コード量 | ❌ 多い – ボイラープレートコードが多め | ✅ 少ない – シンプルな実装で済む |
関心の分離 | ✅ 明確 – UI、ロジック、副作用が完全に分離 | ⚠️ 中程度 – ViewModelにロジックが集中しがち |
パフォーマンス | ⚠️ 注意が必要 – 不適切な実装でパフォーマンス問題が発生する可能性 | ✅ 良好 – 一般的に軽量 |
チーム開発 | ✅ 適している – ルールが明確で一貫性を保ちやすい | ❌ ばらつきが生じやすい – 実装方法が開発者によって異なる |
小規模アプリ | ❌ オーバーエンジニアリング – シンプルなアプリには重すぎる | ✅ 適している – 素早く開発できる |
大規模アプリ | ✅ 優秀 – 複雑な状態管理も破綻しにくい | ❌ 破綻しやすい – 状態管理が複雑になりがち |