Swift Academy · レッスン

Effectと依存関係

副作用を処理し、依存関係を注入します。

レッスン 3/413 ステップ

「Effectと依存関係」はCoddyKit上の無料Swift Academyレッスンです。 これはレッスン3/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはSwift Academy学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 Swift Academyコースには全4レッスンが含まれています。

Effectが存在する理由

Reducerは純粋でなければなりません。つまり、StateとActionを受け取ったら、Stateの変更だけを行います。しかし、実際のアプリにはネットワーク リクエストやタイマーなどの副作用が必要です。TCAではこれらをReducerが返すEffectとしてモデル化し、変更処理の純粋性を保ちながら非同期処理を分離して実行します。

この分離によって、機能をテストしやすくなります。

import ComposableArchitecture

// Reducer stays pure; side effects are values it returns.
// Returning .none means no further work.

.noneの返却

ActionがStateを変更するだけで、後続の処理が不要な場合、Reducerは .none を返します。これは最も一般的なEffectで、実行すべき非同期処理がないことを示します。

すでに見たように、これは各分岐が最終的に戻る基本のEffectです。

import ComposableArchitecture

Reduce { state, action in
    switch action {
    case .incrementTapped:
        state.count += 1
        return .none
    }
}

非同期処理のためのEffect.run

非同期処理を実行するには、.run を返します。そのクロージャは send ハンドルを受け取り、これを使って結果を新しいアクションとしてシステムに送り返します。

ここでは、fetchアクションでデータを読み込み、完了時にレスポンスアクションを送信します。

import ComposableArchitecture

Reduce { state, action in
    switch action {
    case .reloadTapped:
        return .run { send in
            let value = try await loadNumber()
            await send(.responseReceived(value))
        }
    case let .responseReceived(value):
        state.count = value
        return .none
    }
}

結果をActionとして送り返す

EffectはStateを直接変更できません。代わりに、send を通してActionを送信します。その後ReducerがActionを処理してStateを更新します。このループにより、すべての変更処理をReducer内に保てます。

つまり、ネットワークレスポンスは、Reducerが保存するレスポンスActionになります。

import ComposableArchitecture

.run { send in
    let result = try await fetch()
    await send(.dataLoaded(result))
}
// .dataLoaded is then handled by the reducer to set state.

Effectでエラーを処理する

非同期処理ではエラーがスローされることがあります。Effect内でエラーをcatchして失敗アクションを送信すれば、ReducerでエラーStateへ適切に遷移できます。

.run の省略可能な catch クロージャは、スローされたエラーを受け取ります。

import ComposableArchitecture

.run { send in
    let value = try await loadNumber()
    await send(.responseReceived(value))
} catch: { error, send in
    await send(.loadFailed)
}

依存関係が重要な理由

Reducerがネットワーククライアントを直接呼び出したり、日付を直接読み取ったりすると、テストや動作の制御が難しくなります。TCAでは依存関係によってこの問題を解決します。依存関係とは、Reducerが @Dependency を通して利用する注入可能なサービスです。

テストでは、予測可能なフェイクに差し替えます。

import ComposableArchitecture

// Instead of calling APIClient.shared directly,
// inject it as a dependency so tests can replace it.

@Dependencyプロパティラッパー

Reducer内で @Dependency を使って依存関係を宣言し、依存関係の値におけるキーパスで指定します。ライブラリには、clockやuuid generatorなどの組み込み依存関係が用意されています。

ここでは、Reducerにcontinuous clockを組み込みます。

import ComposableArchitecture

@Reducer
struct TimerFeature {
    @ObservableState
    struct State: Equatable { var seconds = 0 }
    enum Action { case start, tick }

    @Dependency(\.continuousClock) var clock

    var body: some ReducerOf<Self> {
        Reduce { state, action in .none }
    }
}

Effectで依存関係を使う

宣言した依存関係は .run の中で使用します。clockが注入されているため、テストでは必要なタイミングで時間を進められるテスト用clockを指定できます。

ここでは、注入されたclockを使ってタイマーを1秒ごとにティックさせます。

import ComposableArchitecture

var body: some ReducerOf<Self> {
    Reduce { state, action in
        switch action {
        case .start:
            return .run { send in
                for await _ in self.clock.timer(interval: .seconds(1)) {
                    await send(.tick)
                }
            }
        case .tick:
            state.seconds += 1
            return .none
        }
    }
}

独自の依存関係を定義する

独自の依存関係を登録するには、値を DependencyKey に準拠させ、liveValue を提供します。次に DependencyValues に計算プロパティを追加して、キーパスからアクセスできるようにします。

ここでは、APIクライアントを独立した依存関係として登録します。

import ComposableArchitecture

struct NumberClient {
    var fetch: () async throws -> Int
}

extension NumberClient: DependencyKey {
    static let liveValue = NumberClient {
        try await loadNumber()
    }
}

extension DependencyValues {
    var numberClient: NumberClient {
        get { self[NumberClient.self] }
        set { self[NumberClient.self] = newValue }
    }
}

独自の依存関係を注入する

キーパスを用意したら、Reducerで @Dependency(\.numberClient) としてクライアントを宣言し、Effect内で呼び出します。本番環境ではlive valueが使われ、テストでは上書きできます。

ここでは、reloadアクションが注入されたクライアントを通してデータを取得します。

import ComposableArchitecture

@Reducer
struct NumberFeature {
    @ObservableState
    struct State: Equatable { var value = 0 }
    enum Action { case reload, response(Int) }

    @Dependency(\.numberClient) var numberClient

    var body: some ReducerOf<Self> {
        Reduce { state, action in
            switch action {
            case .reload:
                return .run { send in
                    let n = try await numberClient.fetch()
                    await send(.response(n))
                }
            case let .response(n):
                state.value = n
                return .none
            }
        }
    }
}

副作用のキャンセル

タイマーのように長時間実行される副作用は、キャンセルできるようにする必要があります。副作用に cancellable idを付け、別のアクションから .cancel を使って後でキャンセルします。

ここでは、タイマーを必要なときに停止できます。

import ComposableArchitecture

enum CancelID { case timer }

// Start a cancellable effect:
.run { send in /* ... */ }
    .cancellable(id: CancelID.timer)

// Stop it from another action:
.cancel(id: CancelID.timer)

クイックチェック:副作用と結果

副作用がどのように結果をリデューサーへ返すかを思い出してください。

まとめ:副作用と依存関係

TCAで副作用と依存性の注入を扱う方法を学びました。

  • リデューサーは純粋性を保ち、Effect 値を返します。.none は処理がないことを意味します。
  • .run は非同期処理を実行し、await send(.action) を通じて結果を返します。
  • エラーは副作用の catch クロージャで処理します。
  • @Dependency を使うと、clockや独自のクライアントなどのサービスを注入できます。これらは DependencyKey と DependencyValues を使って宣言します。
  • 長時間実行される副作用にはidを指定して cancellable を設定できます。

次は、フィーチャー同士を組み合わせ、徹底的にテストします。

import ComposableArchitecture

// Recap: pure reducer + Effect.run for async work
// + @Dependency for testable, injectable services.
無料で開始

AI チューターと学ぶ Swift — 無料

ブラウザでリアルコードを書いて実行し、24/7 の AI チューターから瞬時にサポートを受け、ウェブまたはアプリで続きから学習できます。

コース
122
レッスン
409

よくある質問

「Effectと依存関係」レッスンは無料ですか?

はい。「Effectと依存関係」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、Swift Academyコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 Swift Academyコースには全4レッスンが含まれています。

「Effectと依存関係」で何を学びますか?

副作用を処理し、依存関係を注入します。 ブラウザで直接実行するハンズオンコードでSwift Academyを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。

Swift Academyを始めるのに経験は必要ですか?

事前経験は必要ありません。CoddyKitのSwift Academyは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン3/4です。

「Effectと依存関係」レッスンにはどのくらい時間がかかりますか?

ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。

このSwift Academyレッスンでコードを書いて実行できますか?

はい。すべてのSwift Academyレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。

このコースのすべてのレッスン

  1. State、Action、Reducer
  2. StoreとSwiftUIの統合
  3. Effectと依存関係
  4. 機能の合成とテスト
← Swift Academyに戻る