iOS画面実装のアーキテクチャ・設計思想スキル。Reduxパターンに基づくSingle Root State、Reducerによるビジネスロジック分離、テスタビリティを重視した実装を支援。使用シーン:(1)「このプロトタイプを本番実装にして」などの本格実装リクエスト (2)「状態管理を整理して」などのアーキテクチャ適用リクエスト (3) 画面の状態管理やテスト可能な設計が必要な場合 (4)「この画面をテストしやすくして」などのリファクタリングリクエスト
This skill inherits all available tools. When active, it can use any tool Claude has access to.
references/state-management.mdreferences/testing-guide.mdReduxパターンによる画面実装の設計指針。
JavaScriptエコシステムで生まれた状態管理パターン。3つの原則に基づく:
iOSではTCA (The Composable Architecture) が有名だが、このスキルではTCAを使わずに同じ思想を実現する。
App/
├── AppState.swift # ルート状態(Single Source of Truth)
├── AppAction.swift # 全アクション定義
├── AppStore.swift # Store(状態保持 + Action dispatch)
├── Features/
│ ├── Order/
│ │ ├── OrderState.swift # 機能別の状態(AppStateの一部)
│ │ ├── OrderAction.swift # 機能別のアクション
│ │ ├── OrderReducer.swift # 純粋関数(テスト対象)
│ │ └── OrderView.swift # View(パラメータを受け取るだけ)
│ └── Profile/
│ └── ...
└── Services/ # 副作用(API、DB等)
└── OrderService.swift
struct AppState {
var order = OrderState()
var profile = ProfileState()
var navigation = NavigationState()
}
struct OrderState {
var orders: [Order] = []
var isLoading = false
var error: Error?
}
状態は値型(struct)で定義。AppStateが唯一の真実の源。
enum AppAction {
case order(OrderAction)
case profile(ProfileAction)
}
enum OrderAction {
case loadOrders
case ordersLoaded([Order])
case ordersFailed(Error)
case selectOrder(Order)
}
ビジネスロジックを純粋関数として定義。テストの主要対象。
enum OrderReducer {
static func reduce(state: inout OrderState, action: OrderAction) {
switch action {
case .loadOrders:
state.isLoading = true
state.error = nil
case .ordersLoaded(let orders):
state.orders = orders
state.isLoading = false
case .ordersFailed(let error):
state.error = error
state.isLoading = false
case .selectOrder(let order):
// 選択状態の更新など
break
}
}
}
enum AppReducer {
static func reduce(state: inout AppState, action: AppAction) {
switch action {
case .order(let action):
OrderReducer.reduce(state: &state.order, action: action)
case .profile(let action):
ProfileReducer.reduce(state: &state.profile, action: action)
}
}
}
状態の保持とAction dispatchを担当。副作用(API呼び出し等)もここで処理。
@MainActor
final class AppStore: ObservableObject {
@Published private(set) var state = AppState()
// Dependencies
private let orderService: OrderServiceProtocol
init(orderService: OrderServiceProtocol) {
self.orderService = orderService
}
func send(_ action: AppAction) {
// 1. Reducerで状態更新
AppReducer.reduce(state: &state, action: action)
// 2. 副作用の実行
Task {
await handleSideEffects(action)
}
}
private func handleSideEffects(_ action: AppAction) async {
switch action {
case .order(.loadOrders):
do {
let orders = try await orderService.fetchOrders()
send(.order(.ordersLoaded(orders)))
} catch {
send(.order(.ordersFailed(error)))
}
default:
break
}
}
}
Viewはパラメータを受け取り、表示とアクション送信のみを担当。
@main
struct MyApp: App {
@StateObject private var store = AppStore(
orderService: OrderService()
)
var body: some Scene {
WindowGroup {
RootView()
.environmentObject(store)
}
}
}
struct OrderListView: View {
// 必要な値だけを受け取る
let orders: [Order]
let isLoading: Bool
let onOrderTap: (Order) -> Void
let onRefresh: () -> Void
var body: some View {
Group {
if isLoading {
ProgressView()
} else {
List(orders) { order in
OrderRow(order: order)
.onTapGesture { onOrderTap(order) }
}
.refreshable { onRefresh() }
}
}
}
}
// 親から呼び出す
struct OrderContainerView: View {
@EnvironmentObject var store: AppStore
var body: some View {
OrderListView(
orders: store.state.order.orders,
isLoading: store.state.order.isLoading,
onOrderTap: { store.send(.order(.selectOrder($0))) },
onRefresh: { store.send(.order(.loadOrders)) }
)
}
}
store.state.order.ordersが変わればSwiftUIが自動で差分更新プロトタイプ内の@StateをFeature Stateに移動:
// Before(プロトタイプ)
struct OrderView: View {
@State private var orders: [Order] = []
@State private var isLoading = false
}
// After(Feature State)
struct OrderState {
var orders: [Order] = []
var isLoading = false
}
ユーザー操作とイベントをAction enumに。非同期結果も含める:
enum OrderAction {
// ユーザー操作
case loadOrders
case selectOrder(Order)
// 非同期結果
case ordersLoaded([Order])
case ordersFailed(Error)
}
状態更新ロジックを純粋関数に:
enum OrderReducer {
static func reduce(state: inout OrderState, action: OrderAction) {
// 同期的な状態更新のみ
}
}
Storeで非同期処理を実行:
private func handleSideEffects(_ action: AppAction) async {
// API呼び出しなど
}
Viewをパラメータ受け取り形式に変換。
Reducerのテストを作成(純粋関数なのでテストしやすい)。
@Stateを持たず、パラメータを受け取っているか| ファイル | 内容 |
|---|---|
references/state-management.md | 状態設計の詳細パターン |
references/testing-guide.md | Reducerテストの書き方 |