swift-architecture について
swift-architecture は、状態や依存関係、副作用などの具体的な要件に基づき、Swift アプリのアーキテクチャ選択と移行を支援するスキルです。単純な SwiftUI 機能では MV を起点とし、複雑さに応じた段階的な拡張方法を示します。
主な機能
- 01実際の複雑さに応じたパターン選択
- 02SwiftUI の出発点として MV を採用
- 03状態所有者と依存境界の明確化
- 04機能単位の段階的な移行計画
- 05状態と依存失敗のテストによる検証
活用シーン
- 01SwiftUI 機能のアーキテクチャ選定
- 02MV から TCA へ進む時期の判断
- 03既存機能境界の段階的な移行
- 04依存性注入と状態遷移の設計確認
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ dpearson2699/ swift-ios-skills --skill swift-architectureインストール後、Claude Code で/swift-architectureを入力して読み込まれたか確認します。
同梱ファイルevals/evals.jsonreferences/architecture-patterns.md
詳細
Swift アーキテクチャ
状態の所有者、依存関係、副作用、テストを明確にできる最小限のアーキテクチャを選択します。新しい SwiftUI 機能では MV を既定とし、実際に複雑さが確認された場合にのみ、より高度なパターンへ移行します。
目次
適用範囲の境界
このスキルでは、パターンの選択、モジュール境界、依存関係の方向、移行戦略、アーキテクチャレベルでのテスト境界を扱います。SwiftUI のプロパティラッパーの接続やビューの構成については swiftui-patterns に、ナビゲーション API やルートモデルについては swiftui-navigation に、分離に関する診断については swift-concurrency に、テストの構文やフィクスチャについては swift-testing に任せます。
判断のワークフロー
- 機能の状態の所有者、入力、出力、依存関係、副作用、ナビゲーションの受け渡し、現在のテストを記録します。
- 具体的な課題を特定します。複雑な状態マシン、共有される派生状態、依存関係の制御、機能の合成、チームの担当範囲、UIKit のナビゲーションなどです。
- その課題に対応する最小限のパターンを選び、追加するものと変更しないものを書き出します。
- 依存関係を注入し、状態遷移を観察できるようにしたうえで、垂直スライスを 1 つ実装します。
- 既存の動作テストに加えて、状態遷移と依存関係の失敗に関するテストを実行します。動作が変わった場合はフィクスチャを元に戻し、最小限の境界を修正してから、別のスライスの移行に進む前に再実行します。
パターンの選択
| パターン | 選択する場合 | 主なコスト |
|---|---|---|
| MV | SwiftUI 機能の状態とオーケストレーションが単純な場合 | 分解しないと、ロジックが大きなビューに入り込む可能性があります |
| MVVM | プレゼンテーションロジックに、独立してテスト可能なアダプターが必要な場合 | 余分なレイヤーが処理を転送するだけの殻になる可能性があります |
| MVI | 機能を明示的な state + intents + reducer/effects としてモデル化するのが最適な場合 | ボイラープレートと、遷移を一元的に設計するコストがかかります |
| TCA | 多くの合成可能な機能で、決定論的なエフェクト、依存関係、テストが必要な場合 | フレームワークの習得と、アーキテクチャへのコミットメントが必要です |
| Clean Architecture | 大規模なプロダクトで、domain/data/UI 間の厳格な依存関係の方向が必要な場合 | プロトコルとマッピングのオーバーヘッドがかかります |
| Coordinator | UIKit またはハイブリッドのナビゲーションで、フローを別個の所有者に管理させる必要がある場合 | ライフサイクルとルーティングの所有者が増えます |
| VIPER | 確立された VIPER の境界を持つ既存の UIKit モジュールを保守する場合 | 非常に大掛かりで、新しい SwiftUI 機能の既定には適しません |
ナビゲーションの複雑さが課題である場合は、別の状態パターンとともに Coordinator を使用します。Coordinator は domain/state アーキテクチャの代替ではありません。
MV を既定とする
ビューは状態を表現するものにし、ビジネス操作は監視可能なモデルと注入されたサービスに配置します。
@MainActor
@Observable
final class TripStore {
private let client: TripClient
var trips: [Trip] = []
var error: Error?
init(client: TripClient) { self.client = client }
func load() async {
do { trips = try await client.fetchTrips() }
catch { self.error = error }
}
}
struct TripList: View {
@State private var store: TripStore
init(client: TripClient) {
_store = State(initialValue: TripStore(client: client))
}
var body: some View {
List(store.trips) { Text($0.name) }
.task { await store.load() }
}
}
MVVM、MVI、TCA、Clean Architecture、Coordinator、VIPER の構成については、Architecture Pattern Recipes を読み込みます。
より高度なパターンを選ぶ判断材料
- レンダリングせずにテストする必要のある実質的なプレゼンテーション変換があり、アダプターが実際の振る舞いを担う場合は、MVVM を選びます。
- 遷移、無効な状態、エフェクトに対して、監査可能な reducer のような単一の経路が必要な場合は、MVI を選びます。
- 機能の合成、依存関係のオーバーライド、キャンセル、決定論的なエフェクトテストが複数のモジュールで繰り返し必要な場合は、TCA を選びます。
- 複数の配信レイヤーやデータレイヤーにまたがって、独立した domain ルールと依存関係の方向が重要な場合は、Clean Architecture を選びます。
- UIKit/ハイブリッド環境でルートの所有が必要な場合、複雑なフローを合成する場合、またはビューコントローラーの外部で条件付きナビゲーションを行う場合は、Coordinator を追加します。
- 互換性のあるレガシーモジュールや意図的な移行では VIPER を維持します。慣例だけで新しい SwiftUI 機能に採用してはいけません。
ビューが長いという理由だけで、より高度なパターンに移行してはいけません。まずサブビュー、サービス、目的を絞った監視可能なモデルを切り出します。
移行
機能の境界ごとに一つずつ移行します。
- テストと依存関係/状態の一覧を用意して、動作を固定します。
- 既存の操作を囲む形で、移行先の境界を導入します。
- UI と永続化を同時に書き換えず、状態遷移または依存関係を一つずつ移動します。
- 各段階の後に、動作、ナビゲーション、キャンセル、エラー、永続化の結果を比較します。
- 古い経路に依存する呼び出し元やテストがなくなってから、古い経路を削除します。
ObservableObject から Observation に移行する場合、ラッパーを置き換える前に、同じ所有者と変更の分離を維持します。MVVM から MV に移行する場合、ビューが同じモデル/サービスの動作にバインドされてから、転送のみを行うビューモデルのメンバーを削除します。TCA を導入する場合、まず一つの機能の状態/アクション/エフェクトをラップし、依存関係を段階的に移行します。
よくある間違い
| 間違い | 修正方法 |
|---|---|
| 人気を理由にパターンを選ぶ | 実際に観察された機能上の課題と結び付けます。 |
| ビューモデルがプロパティを転送するだけ | 削除して MV を使います。 |
| 一つのオブジェクトがナビゲーション、ネットワーク、書式設定、永続化、UI 状態をすべて担う | 責務と依存関係の方向に基づいて分割します。 |
| 単純な画面に TCA やクリーンアーキテクチャを適用する | MV から始め、必要に応じて拡張できる境界を維持します。 |
| コーディネーターを状態アーキテクチャとして使う | ルート/ライフサイクルの管理に専念させます。 |
| 一つの機能内に複数のパターンを混在させる | ローカルな状態/エフェクトのモデルを一つ定義し、機能の境界ごとに移行します。 |
| 一括移行 | テスト済みの垂直スライスを一つずつ移動し、同じ検証マトリクスを再実行します。 |
レビューチェックリスト
- 具体的な機能/チーム上の課題に基づいて選択を正当化している
- 状態の所有者、変更経路、依存関係、エフェクト、ナビゲーションの所有者が明確である
- 依存関係が注入され、テストで置き換え可能である
- パターンのコストが機能の複雑さに見合っている
- UI の仕組み、ナビゲーション API、分離、テスト構文については、関連するスキルを参照している
- 一度に一つの垂直スライスを移行し、動作を維持している
- 各段階の後に、失敗、キャンセル、ナビゲーション、永続化の動作を検証している
- 転送のみを行うレイヤーや巨大なオブジェクトが残っていない