【TCA】DependencyClientマクロでボイラープレートを大幅削減

# はじめに
swift-dependenciesの[v1.1.0](https://github.com/pointfreeco/swift-dependencies/releases/tag/1.1.0)で`DependencyClient`というマクロが導入されました。

https://github.com/pointfreeco/swift-dependencies/pull/132

このマクロを使用すると、`"struct of closures"`スタイル[^1]で依存クライアントを実装している場合に記述しているボイラープレートを大幅に削減することができます。

[^1]: TCAやってる人にはお馴染みの、ネットワーク通信等を行うクライアントを構造体で実装する設計パターンを指しています。PRの概要欄にそう書いてあったので引用しました。これが定着すると伝えやすいなあ。

# マクロ導入前のコード
Githubの検索APIをリクエストするクライアントの実装を考えます。

インタフェースは例えばこんな感じになるでしょう。

```swift
import Dependencies

public struct GithubClient: Sendable {
    public var searchRepos: @Sendable (SearchReposParams) async throws -> SearchReposResponse

    public init(searchRepos: @Sendable @escaping (SearchReposParams) async throws -> SearchReposResponse) {
        self.searchRepos = searchRepos
    }

    public struct SearchReposParams: Equatable {
        public let query: String
        public let page: Int

        public init(
            query: String,
            page: Int
        ) {
            self.query = query
            self.page = page
        }
    }
}

extension GithubClient: TestDependencyKey {
    public static let testValue: GithubClient = .init(
        searchRepos: unimplemented("\(Self.self).searchRepos")
    )

    public static let previewValue: GithubClient = .init(
        searchRepos: unimplemented("\(Self.self).searchRepos")
    )
}

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

Reducerでは以下のように呼び出します。

```swift
@Dependency(\.githubClient) var githubClient

.run { [query = state.query, page = state.currentPage] send in
    await send(.searchReposResponse(Result {
        try await githubClient.searchRepos(.init(query: query, page: page))
    }))
}
```

`searchRepos`のクロージャの引数をあえてSearchReposParamsという構造体にしているのは、上記のようにラベル付き引数を使いたいためです。

関数型はラベル付き引数を持つことが出来ないので、構造体にしないとすると`(_ query: String, _ page: Int) async throws -> SearchReposResponse`という型になります。
こうすると呼び出し側のコードは以下のようになりますが、ラベルがある方が実装時も迷わないし可読性が高くなります。また、同じ型の引数が複数並ぶ場合に順序を間違えてしまうリスクもあります。
このため、自分が関わっているプロジェクトでは引数を構造体で定義するようにしています。

```swift
try await githubClient.searchRepos(query, page)
```

# DependencyClientマクロを導入する
DependencyClientマクロを導入することで、上記のコードがどう変わるか見ていきましょう。

なお、DependencyClientマクロは`Dependencies`とは別のモジュールに入っているので、使用する場合は`DependenciesMacros`を追加しましょう。

```swift
.target(
    name: "GithubClient",
    dependencies: [
        .product(name: "Dependencies", package: "swift-dependencies"),
        .product(name: "DependenciesMacros", package: "swift-dependencies")  // 追加
    ]
)    
```

モジュールを追加したら、インタフェースとなる構造体に`@DependencyClient`をつけます。


```diff
import Dependencies
+ import DependenciesMacros

+ @DependencyClient
public struct GithubClient: Sendable {
    // ...
}
```


DependencyClientマクロは以下３つの機能を提供します。

1. 各クロージャエンドポイントの`"unimplemented"`実装を追加してくれる
2. 各クロージャエンドポイントをラベル付き引数を持つメソッドに変換してくれる
3. publicイニシャライザを追加してくれる

## 各クロージャエンドポイントの`"unimplemented"`実装を追加してくれる
すなわち`testValue`プロパティのデフォルト実装を追加してくれるということです。
以下のように`Self()`を代入するコードを実装すると、マクロが`"unimplemented"`実装を追加してくれます。

エンドポイントが大量にある場合、コード量が一気に減りますね。

```diff
extension GithubClient: TestDependencyKey {
-   public static let testValue: GithubClient = .init(
-       searchRepos: unimplemented("\(Self.self).searchRepos")
-   )
+   public static let testValue = Self()


-   public static let previewValue: GithubClient = .init(
-       searchRepos: unimplemented("\(Self.self).searchRepos")
-   )
+   public static let previewValue = Self()
}
```

## 各クロージャエンドポイントをラベル付き引数を持つメソッドに変換してくれる
関数型はラベル付き引数を持つことが出来ないことについて前述しましたが、まさにこの問題を解決してくれます。

エンドポイントの型を以下のように定義します。

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

すると以下のように呼び出すことができます。

```swift
try await githubClient.searchRepos(query: query, page: page)
```

引数として構造体を持たせる形だと`.init`を書く必要があって冗長だったんですが、マクロを使うとベストな形になりますね。

## publicイニシャライザを追加してくれる
クライアントのインタフェースとライブ実装を別々のモジュールに分けている場合、インタフェースにpublicイニシャライザを実装する必要があります。

マクロを使うとなんとこの実装をごっそり消すことが出来ます。
エンドポイントが多いとこちらもメンテするのは地味に大変だったので、とても嬉しいですね。

```diff
public struct GithubClient: Sendable {
    public var searchRepos: @Sendable (SearchReposParams) async throws -> SearchReposResponse

-   public init(searchRepos: @Sendable @escaping (SearchReposParams) async throws -> SearchReposResponse) {
-       self.searchRepos = searchRepos
-   }
```

# 終わりに
DependencyClientマクロを活用することでボイラープレートを大幅に削減することができるようになりました。

DependencyClientマクロを使ったTCAのサンプルアプリを公開しているので、よかったら合わせて参考にしてください。

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

TCA本体にもReducerマクロが追加されたりと、TCAの世界にもマクロがどんどん進出していますね。

マクロを活用してコードをどんどんスマートにしていきましょう。