
The Composable Architectureを使ってみた
iOSアプリの開発ではMVCやMVP、MVVMなどのGUIアーキテクチャが採用されることが多いが、宣言的UIフレームワーク(同様のフレームワークでJetpackComposeやFlutter、ReactNativeなどがある)であるSwiftUIの登場により、SwiftUI時代に合ったアーキテクチャを検討する必要が出てきた。今回はSwiftUI開発で使えるThe Composable Architectureというアーキテクチャを簡単に紹介する。
宣言的UIとは?
宣言的UIでは、アプリのステータスに応じて、どのようなUIになるべきかを宣言的に記述する。また、リアクティブなデータバインディングで実現されるため、データドリブンな方法と言える。そのため、データの流れやステータスの管理が重要になり、モデル層以下がしっかり設計されたアーキテクチャが必要となる。
The Composable Architectureとは?
The Composable Architecture (TCA)は、2020年にPoint-Freeのサービス提供者たちによって提案されたアーキテクチャーであり、FluxやReduxの考え方をもとにSwiftやAppleプラットフォームで使いやすいように開発されたもの。
FluxはFacebook社が提案したWebアプリ向けのアーキテクチャで、MVCなどでは複雑化しがちなデータの流れを単一方向にまとめた単方向なデータフローを実現する。一方で、ReduxはFluxをもとに設計されたライブラリの1つで、より厳格な制約(三原則)を設けている。
TCAでは、Reduxと同様にStore, State, Action, Reducerがあり、Viewは直接Stateを更新できなく、Actionを発行してReducerがStoreのStateを更新する流れとなっている。さらに、副作用を表すEffectや依存関係を保持するEnvironmentがある。

基本的な使い方
TCAを使って簡単な時計アプリを作ってみた。
インストール
TCAはSwift Package Managerに対応しているのでFile > Add PackagesからこちらのURLを入力すると、TCAを取得してインストールできる。
次にStateとActionを定義して、ReducerでActionに対応した処理を追加する。今回は時計アプリなので、定期的に時刻表示を更新する必要があるため、タイマー処理のEffectを追加している。最後にViewでStore変数を定義し、bodyの中でViewStoreのプロパティを参照したり、ViewStore#send()でアクションを発行すればよい。
State,Actionの定義
アプリや機能で管理・参照するState、発生するActionを定義する。
/// 時制
enum HourClock {
/// 12時制 (AMPM)
case twelve
/// 24時制
case twentyFour
}
enum AMPM {
case am
case pm
}
/// State
struct ClockState: Equatable {
/// タイマー起動中かどうか
var isTimerActive = false
/// 現在時刻 (HH:mm:ss)
var time = "00:00:00"
/// 時制
var hourClock: HourClock = .twentyFour
/// AMPM
var ampm: AMPM? = nil
}
/// Action
enum ClockAction: Equatable {
/// 時計スタート
case clockStarted
/// 時計の動作
case clockTicked
/// 時制の切り替え
case hourClockToggled
}
/// Environment
struct ClockEnvironment {
var mainQueue: AnySchedulerOf<DispatchQueue>
}
Reducer
各Actionに対応する処理を記述し、その処理内でStateを更新する。また、必要に応じてAPIリクエストなどのEffectを返却する。
import Foundation
import ComposableArchitecture
let clockReducer = Reducer<ClockState, ClockAction, ClockEnvironment> {
state, action, environment in
struct timerId: Hashable {}
switch action {
// 時計スタート
case .clockStarted:
state.isTimerActive.toggle()
// Effectとしてタイマーを定義
return state.isTimerActive
? Effect.timer(
id: timerId(),
every: 0.1,
tolerance: .zero,
on: environment.mainQueue
)
.map { _ in ClockAction.clockTicked }
: Effect.cancel(id: timerId())
// 時計の動作
case .clockTicked:
let now = Date()
var hour = Calendar.current.component(.hour, from: now)
let minute = Calendar.current.component(.minute, from: now)
let second = Calendar.current.component(.second, from: now)
if state.hourClock == .twelve {
// 12時制の場合
if hour >= 12 {
state.ampm = .pm
hour -= 12
} else {
state.ampm = .am
}
} else {
state.ampm = nil
}
state.time = String(format: "%02d:%02d:%02d", hour, minute, second)
return .none
// 時制の切り替え
case .hourClockToggled:
switch state.hourClock {
case .twelve:
state.hourClock = .twentyFour
case .twentyFour:
state.hourClock = .twelve
}
return .none
}
}
View
Storeをプロパティとして持ち、WithViewStore内でViewを定義することで、Storeを通してStateを参照したりActionを発行できる。
import SwiftUI
import ComposableArchitecture
struct ClockView: View {
let store: Store<ClockState, ClockAction>
let fontSizeLarge: CGFloat = 80
let fontSizeSmall: CGFloat = 48
var body: some View {
// StoreをObservableなViewStoreに変換する構造体で、StateをもとにViewが更新される。
WithViewStore(self.store) { viewStore in
VStack(alignment: .leading) {
// AMPM表示
HStack(spacing: 4) {
Text("AM").foregroundColor(viewStore.state.ampm == .am ? .black : .gray)
Text("PM").foregroundColor(viewStore.state.ampm == .pm ? .black : .gray)
}.onTapGesture {
// 時制切り替えのアクションを発行
viewStore.send(ClockAction.hourClockToggled)
}
// 時刻表示
Text(viewStore.time)
.font(Font(UIFont.monospacedDigitSystemFont(ofSize: fontSizeLarge, weight: .light)))
}
.onAppear {
// 時計スタートのアクションを発行
viewStore.send(ClockAction.clockStarted)
}
}
}
}
以上で画像のような簡単な時計アプリができ、AMPMをタップで12時制/24時制の切り替えができる。

まとめ
The Composable ArchitectureはMVPやMVVMなどのGUIアーキテクチャとは異なり、データの流れに注目し、単一方向データフローのFluxやReduxをもとに開発されたシステムアーキテクチャである。SwiftUI開発では、今まで以上にデータの流れやステータスの管理が大切になってくることから、SwiftUI時代のアーキテクチャとしてTCAが注目されている。