🦜

FlutterエンジニアのためのSwiftUI & TCA入門 Part1

2025/01/18に公開

FlutterエンジニアのためのSwiftUI & TCA入門 Part1: Riverpod/HooksユーザーがモダンなSwift開発を始めるためのガイド

はじめに

※自分の学習用メモとしての意味合いが強いです🚶

Part1である今回は、FlutterでRiverpodやflutter_hooksを使った状態管理に慣れているエンジニアが、iOS開発の最新トレンドであるSwiftUIとTCA(The Composable Architecture)を使った開発に挑戦するためのガイドです。

このガイドでは、SwiftUIの基本的な概念から、TCAのアーキテクチャ、そしてFlutterでの経験を活かすための具体的な方法まで、幅広く解説していきます。

なぜSwiftUIとTCAなのか?

SwiftUIの魅力

  • 宣言的なUI: Flutterと同様に、SwiftUIも宣言的なUIフレームワークです。UIをどのように表示するかを記述することで、フレームワークが自動的にUIをレンダリングしてくれます。
  • リアクティブなUI: 状態の変化に応じてUIが自動的に更新されるため、複雑なUIの管理が容易になります。
  • iOS、macOS、watchOS、tvOSに対応: 一つのコードベースで複数のプラットフォームに対応できるため、開発効率が向上します。
  • Appleのエコシステムとの親和性: Appleの最新技術を最大限に活用できるため、高品質なアプリ開発が可能です。

TCAの魅力

  • 予測可能な状態管理: TCAは、状態、アクション、副作用を明確に分離することで、複雑な状態管理をシンプルにします。
  • テスト容易性: 各コンポーネントが独立しているため、ユニットテストが容易に行えます。
  • デバッグの容易性: TCAのアーキテクチャにより、状態の変化と副作用を追跡しやすくなります。
  • 大規模アプリ開発: 複雑なアプリケーションでも、一貫性のあるアーキテクチャで開発を進めることができます。

SwiftUIの基礎

プロジェクト作成

まずは新しいSwiftUIプロジェクトを作成しましょう。Xcodeを起動し、「Create a new Xcode project」を選択します。
iOS > Appを選択し、プロジェクト名を入力してNextをクリック。InterfaceSwiftUIを選択してプロジェクトを作成します。

基本的な構文

SwiftUIは、Viewプロトコルに準拠した構造体やクラスでUIを記述します。

import SwiftUI

struct ContentView: View {
    var body: some View {
        Text("Hello, SwiftUI!")
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}
  • ContentViewは、Viewプロトコルに準拠した構造体で、UIを記述します。
  • bodyプロパティは、UIのレンダリング方法を記述します。
  • Textは、テキストを表示するためのViewです。
  • ContentView_Previewsは、Xcodeのプレビューを表示するための構造体です。

レイアウト

SwiftUIでは、HStack, VStack, ZStackを使ってUIをレイアウトします。

  • HStack (Horizontal Stack): 水平方向にViewを配置します。
  • VStack (Vertical Stack): 垂直方向にViewを配置します。
  • ZStack (Z-Axis Stack): Viewを重ねて配置します。
struct LayoutExampleView: View {
    var body: some View {
        VStack {
            HStack {
                Text("Left")
                Text("Right")
            }
            ZStack {
                Rectangle()
                    .fill(.blue)
                    .frame(width: 100, height: 100)
                Text("Overlay")
                    .foregroundColor(.white)
            }
            Spacer()
            Text("Bottom")
        }
    }
}

状態管理(@State@Binding

SwiftUIでの状態管理は、@State@Bindingを使います。

  • @State: View内部で管理される状態を定義します。状態が変化すると、Viewが再レンダリングされます。
  • @Binding: 親Viewから渡された状態を共有します。親Viewの状態が変化すると、子Viewも自動的に更新されます。
struct StateExampleView: View {
    @State private var count = 0

    var body: some View {
        VStack {
            Text("Count: \(count)")
            Button("Increment") {
                count += 1
            }
            // 子ViewへのBinding
            CounterView(count: $count)
        }
    }
}

struct CounterView: View {
    @Binding var count: Int

    var body: some View {
        Button("Decrement") {
            count -= 1
        }
    }
}

StateExampleViewは、countという状態を持っており、Buttonを押すとcountが増加します。
CounterViewは、@Bindingで親Viewのcountを共有しています。CounterViewのボタンを押すと、親Viewのcountが減少します。

Viewのカスタマイズ

SwiftUIでは、modifierを使ってViewをカスタマイズします。

struct CustomViewExampleView: View {
    var body: some View {
        Text("Custom Text")
            .font(.title)
            .foregroundColor(.white)
            .padding()
            .background(.blue)
            .cornerRadius(10)
    }
}
  • .font(.title)は、フォントサイズをタイトル用に変更します。
  • .foregroundColor(.white)は、テキストの色を白に変更します。
  • .padding()は、テキストの周りに余白を追加します。
  • .background(.blue)は、背景色を青に変更します。
  • .cornerRadius(10)は、角を丸くします。

TCA(The Composable Architecture)の概要

TCAは、状態管理、副作用、テストをシンプルにするためのアーキテクチャです。TCAは、主に以下の要素で構成されています。

  • State: アプリケーションの状態を保持する構造体。
  • Action: ユーザーインタラクションやシステムイベントを表現するenum。
  • Environment: 副作用(API呼び出し、データベースアクセスなど)を処理する依存関係を保持する構造体。
  • Reducer: StateActionを受け取り、新しいStateと副作用を返す関数。
  • Store: State, Action, Environment, Reducerを保持し、状態の変化を管理するクラス。

TCAの基本的な流れ

  1. ユーザーがUIを操作してActionが発生します。
  2. StoreActionReducerに渡します。
  3. Reducerは、現在のStateActionに基づいて、新しいStateと副作用を生成します。
  4. Storeは新しいStateを反映し、必要に応じて副作用を実行します。
  5. UIはStoreから新しいStateを受け取り、再レンダリングされます。

TCAのコード例

import ComposableArchitecture
import SwiftUI

// 1. State
struct CounterState: Equatable {
    var count = 0
}

// 2. Action
enum CounterAction {
    case incrementButtonTapped
    case decrementButtonTapped
}

// 3. Environment (今回は副作用がないので空の構造体)
struct CounterEnvironment {}

// 4. Reducer
let counterReducer = Reducer<CounterState, CounterAction, CounterEnvironment> { state, action, _ in
    switch action {
    case .incrementButtonTapped:
        state.count += 1
        return .none
    case .decrementButtonTapped:
        state.count -= 1
        return .none
    }
}

// SwiftUIでのView実装
struct TCAExampleView: View {
    // Storeの定義
    let store = Store(
        initialState: CounterState(),
        reducer: counterReducer,
        environment: CounterEnvironment()
    )

    var body: some View {
        WithViewStore(store) { viewStore in
          VStack {
            Text("Count: \(viewStore.count)")
              HStack {
                Button("Increment") {
                  viewStore.send(.incrementButtonTapped)
                }
                Button("Decrement") {
                  viewStore.send(.decrementButtonTapped)
                }
              }
          }
        }
    }
}
  • CounterStateは、カウント数を保持する構造体です。
  • CounterActionは、ボタンがタップされたことを表現するenumです。
  • CounterEnvironmentは、副作用がないため空の構造体です。
  • counterReducerは、StateActionに基づいて新しいStateを生成する関数です。
  • TCAExampleViewは、Storeを保持し、UIを構築するViewです。WithViewStoreを使用して、Storeの状態をUIにバインドします。

Riverpod/Hooksとの比較

状態管理

  • Flutter(Riverpod/Hooks):
    • Providerで状態を管理し、Hooksで再利用可能なロジックを記述します。
    • 状態の変化を監視しやすく、UIの再レンダリングを制御できます。
  • SwiftUI(TCA):
    • Stateで状態を保持し、Actionで状態の変化を表現します。
    • Reducerで状態の変化を管理し、副作用を分離します。
    • 予測可能でテストしやすい状態管理を実現します。

副作用

  • Flutter(Riverpod/Hooks):
    • RiverpodのProviderFutureProviderを使って非同期処理を管理します。
    • useEffectを使って副作用を処理します。
  • SwiftUI(TCA):
    • Environmentで副作用の依存関係を管理します。
    • Reducerで副作用を返し、Effectを使って副作用を実行します。
    • 副作用を明確に分離し、テスト容易性を向上させます。

アーキテクチャ

  • Flutter(Riverpod/Hooks):
    • 特定のアーキテクチャに依存せず、柔軟に構成を組み立てられます。
  • SwiftUI(TCA):
    • 厳格な単方向データフローアーキテクチャを採用します。
    • コードの一貫性を維持し、大規模アプリケーション開発に適しています。

Flutterの知識をSwiftUI/TCAで活かす

宣言的なUI

Flutterで慣れ親しんだ宣言的なUIの考え方は、SwiftUIでも同様に適用できます。UIをどのように表示するかを記述することで、フレームワークが自動的にUIをレンダリングしてくれます。

State Management

FlutterでProviderやRiverpodを使って状態管理をしていた方は、TCAの単方向データフローの考え方に比較的スムーズに移行できるはずです。状態、アクション、副作用を明確に分離することで、複雑な状態管理をシンプルにできます。

テスト

Flutterでユニットテストを書いていた経験は、TCAのテスト容易性を最大限に活かすことができます。各コンポーネントが独立しているため、ユニットテストを容易に書くことができます。

まとめ

このドキュメントでは、FlutterエンジニアがSwiftUIとTCAを使ってiOS開発を始めるための基礎知識を解説しました。SwiftUIの宣言的なUIとTCAの単方向データフローアーキテクチャは、Flutterの経験を活かしてスムーズに学習できるはずです。

ぜひ、このガイドを参考にSwiftの世界に飛び込んでみてください。
新しい技術を学ぶことは、エンジニアとしての成長を加速させるはずです。

Discussion