TCAでGithubリポジトリ検索アプリを作ってみよう①

:::note warn
本記事およびサンプルコードはTCA 1.5.0を使用しています
:::

# はじめに
TCAは注目度は高いと思っていますが、僕の（狭い）観測範囲では導入事例はまだあまり多くない印象です。

僕が関わっているプロダクトではガッツリTCAを使っているので、国内でもっともっとTCAが盛り上がってくれないと困る...！w

ということで、国内でのTCAの盛り上がりに少しでも貢献できればと思い、実践的なサンプルアプリケーションを作って解説記事を書いてみることにしました。

ちゃんと解説しようと思うと結構なボリュームになりそうなので、いくつかの記事に分けて公開していく予定です。

https://qiita.com/takehilo/items/c56fbfc92b462bc61b30

# 想定読者
TCAを全く知らない方向けの記事ではありません。
少なくとも[公式チュートリアル](https://pointfreeco.github.io/swift-composable-architecture/main/tutorials/meetcomposablearchitecture)は一通り目を通し、簡単にでも自分でサンプルアプリを作ったことがある方を想定しています。

簡単なサンプルアプリを作れるレベルから一歩先に進むために必要な技術要素を学べることをイメージしています。

# サンプルアプリの概要
サンプルアプリのコードはGithubで公開しています。

https://github.com/takehilo/github-app-with-the-composable-architecture

モバイルアプリ開発者にはお馴染みの、Githubリポジトリ検索機能を持つアプリです。

以下の機能を備えています。

- 任意のクエリでリポジトリを検索できる
- 検索結果をページネートできる
- リポジトリ情報の詳細画面に遷移できる
- リポジトリをお気に入りに登録できる
- お気に入りで検索結果をフィルタできる

![app.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/81715/5ec23348-86ab-53bd-15c7-715b953e3eb5.gif)

本サンプルアプリに取り入れている主なTCAの技術要素は以下の通りです。

- [Navigation](https://pointfreeco.github.io/swift-composable-architecture/main/documentation/composablearchitecture/navigation)
- [Bindings](https://pointfreeco.github.io/swift-composable-architecture/main/documentation/composablearchitecture/bindings)
- [Dependencies](https://pointfreeco.github.io/swift-composable-architecture/main/documentation/composablearchitecture/dependencymanagement)
- [Testing](https://pointfreeco.github.io/swift-composable-architecture/main/documentation/composablearchitecture/testing)

特に、BindingsとDependenciesは公式チュートリアルには取り上げられていない内容なので、参考になれば幸いです。

# 実装解説
ここから実装の解説をしていきます。

解説を読みつつ手を動かすことで理解もより進むかなと思い、前述のGithubリポジトリには[スタータープロジェクト](https://github.com/takehilo/github-app-with-the-composable-architecture/tree/main/starter)を用意しています。  
スタータープロジェクトをベースにご自身で実装を追加し、動くアプリを実際に作ってみてください。

## プロジェクト構成
最初に簡単にプロジェクト構成について触れておきます。
本プロジェクトはSPMを使ったマルチモジュール構成になっています。

SPMについて今は特に深く知っておく必要はありません。
とりあえず役割ごとにコードがモジュールに分かれていて、依存関係がどうなっているかをイメージできればOKです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/81715/20551ccd-24f9-dfb9-68c1-1604f7aa2430.png)

|モジュール名|概要|
|:----|:----|
|App|アプリのエントリポイント|
|SearchRepositoriesFeature|Githubリポジトリ検索＆一覧表示機能|
|RepositoryDetailFeature|Githubリポジトリの詳細情報表示機能|
|SharedModel|各モジュール共通で使用するモデルを格納している|
|GithubClient|Github APIにアクセスするクライアントのインタフェースを持つ|
|GithubClientLive|Github APIにアクセスするクライアントの実装を持つ|
|ApiClient|ApiClientを格納している|

モジュールと依存関係は`Package.swift`に全て記載されています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/81715/9cdba9bc-318b-f254-e4ab-3692619b3ecd.png)

例えば、`SearchRepositoriesFeature`を見てみましょう。  
`dependencies`に依存先のモジュール名が書かれていて、`SharedModel`、`GithubClient`、`RepositoryDetailFeature`の3つのモジュール、そしてライブラリのTCAに依存していることがわかります。

```swift
targets: [
    ...

    .target(
        name: "SearchRepositoriesFeature",
        dependencies: [
            "SharedModel",
            "GithubClient",
            "RepositoryDetailFeature",
            .product(name: "ComposableArchitecture", package: "swift-composable-architecture")
        ],
        swiftSettings: [
            .unsafeFlags([
                "-strict-concurrency=complete"
            ])
        ]
    ),

    ...
]
```

なお、SPMによるマルチモジュール構成のプロジェクトを作成するにあたっては、以下の記事を参考にさせていただきました。

https://zenn.dev/kalupas226/articles/73118709e316ad

前述のスタータープロジェクトはすでにSPMでプロジェクトを構成しているので、`Package.swift`にモジュールを追記していくことですぐにマルチモジュール構成のアプリを作ることができます。

## 依存の設計
まずはGithubClientのコードから見ていきます。
GithubClientはGithub APIにアクセスするクライアントで、リポジトリ検索を行うために使用します。

GithubClientはシンプルな構造体で実装されています。  
DIをするにあたり、依存をインタフェースと実装に分けることはとても一般的です。  
そして、iOSの世界ではそれをプロトコルによって実現するのが一般的かと思いますが、TCAの作者であるPoint-Freeの2人は、**依存を構造体で設計する**ことを推奨しています。

```swift
@DependencyClient
public struct GithubClient: Sendable {
    public var searchRepos: @Sendable (_ query: String, _ page: Int) async throws -> SearchReposResponse
}
```

シンプルな構造体で依存を設計することによる最大のメリットは、**様々なパターンのモック実装を簡単に作れること**です。

例えば、空のレスポンスを返すモック実装、例外をスローするモック実装、アイテムを1つだけ含むレスポンスを返すモック実装、アイテムを100個含むレスポンスを返すモック実装...などです。

```swift
extension GithubClient {
    static let live = Self(searchRepos: { try await ... })  // ライブ実装
    static let empty = Self(searchRepos: { _ in .init(totalCount: 0, items: []) })  // 空のレスポンスを返す
    static let fail = Self(searchRepos: { _ in NSError(domain: "", code: 0) })  // 例外をスローする
}

// ライブ実装をベースにカスタムしたりもできる
var client = GithubClient.liveValue
client.searchRepos = { _ in .init(totalCount: 0, items: []) }
```

プロトコルでこれをやろうとすると、パターンの数だけそのプロトコルに準拠する構造体なりクラスを実装することになります。  
メソッドが1つだけであればまだ良いですが、メソッドが増えてくるとそのパターンは膨大になっていきます。

```swift
protocol GithubClientProtocol {
    func searchRepos(query: String, page: Int) async throws -> SearchReposResponse
}

// ライブ実装
struct LiveGithubClient: GithubClientProtocol {
    func searchRepos(query: String, page: Int) async throws -> SearchReposResponse {
       try await ...
    }
}

// 空のレスポンスを返す
struct EmptyGithubClient: GithubClientProtocol {
    func searchRepos(query: String, page: Int) async throws -> SearchReposResponse {
        .init(totalCount: 0, items: [])
    }
}

// 例外をスローする
struct FailGithubClient: GithubClientProtocol {
    func searchRepos(query: String, page: Int) async throws -> SearchReposResponse {
        throw NSError(domain: "error", code: 0)
    }
}
```

シンプルな構造体による設計は、Xcodeプレビューやテスト実装でそのパワーを発揮します。詳しくは後述します。

<br>

さて、GithubClientのコードをもう少し見ていきましょう。  
以下のコードで、GithubClientを`TestDependencyKey`に準拠させています。

```swift
extension GithubClient: TestDependencyKey {
    public static let testValue = Self()
    public static let previewValue = Self()
}
```

`TestDependencyKey`は[Dependencies](https://github.com/pointfreeco/swift-dependencies)というライブラリが提供するプロトコルです。
DependenciesはPoint-Free製の依存管理ライブラリで、SwiftUIのEnvironmentにインスパイアされて作成されました。

例えば、SwiftUIビューで現在適用されているColorSchemeの値を取り出したいとき、以下のようなコードを実装します。  
これで変数`colorScheme`に値が注入されます。

```swift
struct ContentView: View {
    @Environment(\.colorScheme) var colorScheme: ColorScheme

    var body: some View { ... }
}
```

Dependenciesを利用すると、上記と同じような感覚でReducerにGithubClientのインスタンスをDIすることが可能になります。

```swift
public struct SearchRepositoriesReducer: Reducer {
    ...

    @Dependency(\.githubClient) var githubClient

    ...

    var body: some ReducerOf<Self> { ... }
}
```

DIできるようにするために、もう少し実装が必要です。  
`DependencyValues`にGithubClient型のcomputedプロパティを実装します。

```swift
public extension DependencyValues {
    var githubClient: GithubClient {
        get { self[GithubClient.self] }
        set { self[GithubClient.self] = newValue }
    }
}
```

ここまででGithubClientのインタフェースの実装は完了です。  
次に、GithubClientのライブ実装のコードを見ていきます。

※ライブ実装という言葉がちょこちょこ出てきてますが、これは実際の動くアプリで使用する依存の実装（つまり実際にAPIなどにアクセスする）のことを指しています

GithubClientのライブ実装は、インタフェースとは別のモジュールで実装されています。  
インタフェースはシンプルな構造体ですが、ライブ実装は多くの場合実装が複雑で、3rdパーティライブラリに依存していたりします。  
このため、ライブ実装はインタフェースと比較してコンパイルに時間がかかります。

そこで、**[インタフェースとライブ実装を別々のモジュールに分けることが推奨されています](https://pointfreeco.github.io/swift-dependencies/main/documentation/dependencies/livepreviewtest#Separating-interface-and-implementation)**。  
インタフェースを軽くしておき、Featureモジュールをインタフェースだけに依存させておくことで、Xcodeプレビューが軽量になるなどのメリットがあります。

ライブ実装のモジュールでは、GithubClientを`DependencyKey`に準拠させます。  
`DependencyKey`は`TestDependencyKey`と対をなすプロトコルで、ライブ実装インスタンスを`DependencyValues`に登録するために必要です。  
以下のように`liveValue`プロパティを実装することで、アプリ実行時に`@Dependency`を通じてライブ実装インスタンスがDIされます。

```swift
extension GithubClient: DependencyKey {
    public static let liveValue: GithubClient = .live()

    static func live(apiClient: ApiClient = .liveValue) -> Self {
        .init(
            searchRepos: {
                try await apiClient.send(request: SearchReposRequest(query: $0.query, page: $0.page))
            }
        )
    }
}
```

# 続く...
長くなってきたので、ここらで一旦区切りたいと思います。  

次の記事ではリポジトリ検索画面の解説をする予定です。
お楽しみに！