pointfreeco/swift-composable-architecture
主な特徴
- 単純な値型による状態管理と画面間での状態共有
- 大規模機能を小さなコンポーネントに分解し、独立したモジュールとして抽出・再結合可能
- テスト可能で理解しやすい方法での外部世界との通信(副作用処理)
- 単体テスト、統合テスト、エンドツーエンドテストの包括的サポート
- 最小限のAPIと概念で上記を実現する優れた人間工学
- @Reducer、@ObservableState、@Dependencyなどのマクロによる簡潔な記述
- SwiftUIとUIKitの両方をサポート
- 時間ベースの操作(デバウンス、スロットル、遅延)の制御
- ナビゲーションの統一的な処理(シート、フルスクリーン、プッシュ、ポップオーバー)
リポジトリ解析: pointfreeco/swift-composable-architecture
基本情報
- リポジトリ名: pointfreeco/swift-composable-architecture
- 主要言語: Swift
- スター数: 13,681
- フォーク数: 1,561
- 最終更新: 活発に更新中(v1.17+)
- ライセンス: MIT License
- トピックス: Architecture, Swift, SwiftUI, UIKit, State Management, Testing, Side Effects, Functional Programming, Reactive Programming
概要
一言で言うと
The Composable Architecture(TCA)は、構成可能性、テスト容易性、人間工学を重視して設計された、一貫性があり理解しやすい方法でアプリケーションを構築するためのSwiftライブラリです。
詳細説明
The Composable Architecture(TCA)は、Swift言語で書かれた、統一的で予測可能な方法でアプリケーションを構築するためのライブラリです。Elm言語とReduxアーキテクチャから着想を得て、SwiftとAppleプラットフォームに最適化された形で実装されています。単純な値型を使用した状態管理、機能の分解と合成、テスト可能な副作用処理、そして包括的なテストサポートを提供し、ビジネスロジックが期待通りに動作することを強力に保証します。SwiftUI、UIKit、その他のAppleプラットフォーム(iOS、macOS、iPadOS、visionOS、tvOS、watchOS)で使用できます。
主な特徴
- 単純な値型による状態管理と画面間での状態共有
- 大規模機能を小さなコンポーネントに分解し、独立したモジュールとして抽出・再結合可能
- テスト可能で理解しやすい方法での外部世界との通信(副作用処理)
- 単体テスト、統合テスト、エンドツーエンドテストの包括的サポート
- 最小限のAPIと概念で上記を実現する優れた人間工学
- @Reducer、@ObservableState、@Dependencyなどのマクロによる簡潔な記述
- SwiftUIとUIKitの両方をサポート
- 時間ベースの操作(デバウンス、スロットル、遅延)の制御
- ナビゲーションの統一的な処理(シート、フルスクリーン、プッシュ、ポップオーバー)
使用方法
インストール
前提条件
- Swift 5.9以上
- Xcode 15以上
- iOS 13.0+、macOS 10.15+、tvOS 13.0+、watchOS 6.0+
インストール手順
# Swift Package Manager経由でインストール
# Package.swiftに追加
dependencies: [
.package(url: "https://github.com/pointfreeco/swift-composable-architecture", from: "1.17.0")
]
# または、Xcodeで追加
# 1. File → Add Package Dependencies...
# 2. URL: https://github.com/pointfreeco/swift-composable-architecture
# 3. ComposableArchitectureを選択してターゲットに追加
基本的な使い方
Hello World相当の例
import ComposableArchitecture
import SwiftUI
// Reducerマクロを使用してFeatureを定義
@Reducer
struct Counter {
// 状態を定義(ObservableStateマクロで観察可能に)
@ObservableState
struct State: Equatable {
var count = 0
}
// アクションを定義
enum Action {
case incrementButtonTapped
case decrementButtonTapped
}
// ロジックを実装
var body: some Reducer<State, Action> {
Reduce { state, action in
switch action {
case .incrementButtonTapped:
state.count += 1
return .none
case .decrementButtonTapped:
state.count -= 1
return .none
}
}
}
}
// Viewを定義
struct CounterView: View {
let store: StoreOf<Counter>
var body: some View {
HStack {
Button("-") { store.send(.decrementButtonTapped) }
Text("\(store.count)")
Button("+") { store.send(.incrementButtonTapped) }
}
}
}
// アプリのエントリーポイント
@main
struct MyApp: App {
var body: some Scene {
WindowGroup {
CounterView(
store: Store(initialState: Counter.State()) {
Counter()
}
)
}
}
}
実践的な使用例
// 副作用を含む例:数値に関する豆知識をAPIから取得
@Reducer
struct NumberTrivia {
@ObservableState
struct State: Equatable {
var count = 0
var numberFact: String?
var isLoadingFact = false
}
enum Action {
case incrementButtonTapped
case decrementButtonTapped
case numberFactButtonTapped
case numberFactResponse(Result<String, Error>)
}
// 依存性注入
@Dependency(\.numberFact) var numberFact
var body: some Reducer<State, Action> {
Reduce { state, action in
switch action {
case .incrementButtonTapped:
state.count += 1
return .none
case .decrementButtonTapped:
state.count -= 1
return .none
case .numberFactButtonTapped:
state.isLoadingFact = true
state.numberFact = nil
// 非同期の副作用を実行
return .run { [count = state.count] send in
let result = await Result {
try await self.numberFact.fetch(count)
}
await send(.numberFactResponse(result))
}
case let .numberFactResponse(.success(fact)):
state.isLoadingFact = false
state.numberFact = fact
return .none
case .numberFactResponse(.failure):
state.isLoadingFact = false
state.numberFact = "エラーが発生しました"
return .none
}
}
}
}
高度な使い方
// 複数の機能を組み合わせる例
@Reducer
struct AppFeature {
@ObservableState
struct State: Equatable {
var counter = Counter.State()
var profile = Profile.State()
@Presents var destination: Destination.State?
}
enum Action {
case counter(Counter.Action)
case profile(Profile.Action)
case destination(PresentationAction<Destination.Action>)
case settingsButtonTapped
}
var body: some Reducer<State, Action> {
Scope(state: \.counter, action: \.counter) {
Counter()
}
Scope(state: \.profile, action: \.profile) {
Profile()
}
.ifLet(\.$destination, action: \.destination) {
Destination()
}
Reduce { state, action in
switch action {
case .settingsButtonTapped:
state.destination = .settings(Settings.State())
return .none
case .counter, .profile, .destination:
return .none
}
}
}
}
// テストコード
@Test
func counterIncrements() async {
let store = TestStore(initialState: Counter.State()) {
Counter()
}
await store.send(.incrementButtonTapped) {
$0.count = 1
}
await store.send(.incrementButtonTapped) {
$0.count = 2
}
}
ドキュメント・リソース
公式ドキュメント
- README.md: プロジェクト概要とクイックスタートガイド
- Documentation site: https://pointfreeco.github.io/swift-composable-architecture/ - 完全なAPIドキュメント
- Point-Free episodes: https://www.pointfree.co/collections/composable-architecture - 詳細なビデオチュートリアル
サンプル・デモ
- Examples/CaseStudies/: 基礎から応用までの様々なケーススタディ
- Examples/TicTacToe/: モジュール化されたTic-Tac-Toeゲーム
- Examples/Todos/: Todoアプリケーション
- Examples/VoiceMemos/: 音声メモアプリ(複雑な副作用の例)
- Examples/SyncUps/: 完全なアプリケーションの例
チュートリアル・ガイド
- Getting Started: 基本概念と最初のアプリ構築
- Dependencies: 依存性管理の詳細
- Testing: テスト戦略とベストプラクティス
- Navigation: ナビゲーションパターン
- Performance: パフォーマンス最適化
技術的詳細
アーキテクチャ
全体構造
TCAは単方向データフローアーキテクチャを採用。アプリケーションは4つの主要な概念で構成される:
- State: アプリケーションの状態を表す単純な値型
- Action: 状態を変更できるすべてのイベントを表す列挙型
- Reducer: 現在の状態とアクションから次の状態を計算し、実行すべき副作用を返す純粋関数
- Store: Reducerと副作用を実行し、状態の変更を観察可能にするランタイム
ディレクトリ構成
swift-composable-architecture/
├── Sources/
│ ├── ComposableArchitecture/ # メインライブラリ
│ │ ├── Effect.swift # 副作用の抽象化
│ │ ├── Reducer.swift # Reducer プロトコルと実装
│ │ ├── Store.swift # Store 実装
│ │ ├── ViewStore.swift # View バインディング
│ │ ├── Dependencies/ # 依存性注入システム
│ │ ├── SwiftUI/ # SwiftUI 統合
│ │ ├── UIKit/ # UIKit 統合
│ │ └── Observation/ # Swift 5.9 Observation サポート
│ └── ComposableArchitectureMacros/ # Swift マクロ実装
├── Tests/ # テストスイート
├── Examples/ # サンプルアプリケーション
└── Documentation.docc/ # DocC ドキュメント
主要コンポーネント
-
Reducer Protocol: アプリケーションロジックを定義する中心的なプロトコル
- 場所:
Sources/ComposableArchitecture/Reducer.swift - 依存: State、Action、Effect
- インターフェース:
body: some Reducer<State, Action>
- 場所:
-
Store: アプリケーションのランタイム
- 場所:
Sources/ComposableArchitecture/Store.swift - 依存: Reducer、State
- インターフェース:
send(_:),scope(state:action:)
- 場所:
-
Effect: 副作用の表現と実行
- 場所:
Sources/ComposableArchitecture/Effect.swift - 依存: なし(独立した値型)
- インターフェース:
.none,.run,.send
- 場所:
技術スタック
コア技術
- 言語: Swift 5.9+、最新のSwift機能(マクロ、Observation)を活用
- フレームワーク: SwiftUI、UIKit、Combine
- 主要ライブラリ:
- swift-dependencies (1.4.0+): 依存性注入
- swift-case-paths (1.5.4+): 列挙型の処理
- swift-perception (1.3.4+): 観察機能のバックポート
- swift-navigation (2.3.0+): ナビゲーション統合
開発・運用ツール
- ビルドツール: Swift Package Manager
- テスト: XCTest、カスタムTestStore
- CI/CD: GitHub Actions
- ドキュメント: DocC、GitHub Pages
設計パターン・手法
- 単方向データフロー(Elm Architecture/Redux パターン)
- 関数型プログラミング(純粋関数、不変性)
- プロトコル指向プログラミング
- 依存性注入
- 型安全性の徹底活用
データフロー・処理フロー
- ユーザーインタラクションやシステムイベントがActionを生成
- StoreがActionを受け取り、現在のStateとともにReducerに渡す
- Reducerが新しいStateを計算し、必要な副作用(Effect)を返す
- Storeが新しいStateを保存し、ViewやViewStoreに通知
- 副作用が実行され、結果が新たなActionとしてStoreに送信
API・インターフェース
公開API
Store
- 目的: アプリケーションの状態管理とアクション処理
- 使用例:
let store = Store(initialState: Feature.State()) {
Feature()
._printChanges() // デバッグ用
}
// アクションの送信
store.send(.buttonTapped)
// 状態の観察(SwiftUI)
Text("\(store.count)")
設定・カスタマイズ
設定ファイル
// 依存性の登録
extension DependencyValues {
var apiClient: APIClient {
get { self[APIClient.self] }
set { self[APIClient.self] = newValue }
}
}
// テスト用のモック
store.dependencies.apiClient = .mock
拡張・プラグイン開発
カスタムReducer修飾子、依存性、Effectの作成が可能
パフォーマンス・スケーラビリティ
パフォーマンス特性
- 状態の変更は同期的で予測可能
- Observation統合により必要な部分のみ再レンダリング
- IdentifiedArrayCollectionsによる効率的なコレクション操作
スケーラビリティ
モジュール化により、大規模アプリケーションでも管理可能。各機能を独立したモジュールとして開発し、後で統合
制限事項
- 学習曲線が急(関数型プログラミングの概念)
- ボイラープレートコードがやや多い(マクロで軽減)
- Appleプラットフォーム専用
評価・所感
技術的評価
強み
- 極めて高いテスタビリティ(時間ベースの操作も制御可能)
- 予測可能で一貫性のあるアーキテクチャ
- 優れたモジュール性と再利用性
- 型安全性による実行時エラーの削減
- 活発なコミュニティと継続的な改善
改善の余地
- 初学者にとっての学習コストが高い
- 小規模アプリケーションには過剰な場合がある
- パフォーマンス最適化が必要な場合の複雑さ
向いている用途
- 中〜大規模のiOS/macOSアプリケーション
- 複雑な状態管理が必要なアプリ
- チーム開発でアーキテクチャの統一が重要な場合
- テストカバレッジを重視するプロジェクト
向いていない用途
- プロトタイプや小規模アプリ
- 関数型プログラミングに不慣れなチーム
- パフォーマンスが最重要なリアルタイムアプリ
総評
The Composable Architectureは、Swift/iOS開発における最も洗練されたアーキテクチャフレームワークの1つです。関数型プログラミングの原則に基づいた設計により、予測可能で保守しやすく、テスト可能なアプリケーションの構築を可能にします。学習コストは高いものの、中〜大規模プロジェクトでは、その投資に見合う価値を提供します。Point-Freeチームによる継続的な改善と、活発なコミュニティのサポートにより、今後も進化し続けることが期待されます。