【TCA】Sendableと@SendableとTCA

# はじめに
TCAを使ってアプリを実装しているときによく出くわすあの書き方は、`Sendable`を理解することでよく理解できるようになります。
本記事ではまず`Sendable`について解説し、その後TCAで`Sendable`に出くわすポイントについて解説してみたいと思います。

※説明不足、間違い等々ありましたら、是非コメントください

# Sendableとは？
準拠する型が**データ競合が発生せず安全に渡せるデータ**であることを表すものです。
Swiftのドキュメントでは、**ある同時実行ドメインから別の同時実行ドメインに共有できる型**と表現されています。

# データ競合（Data Races）とは？
複数のスレッドが同時に同じデータにアクセスし、かつそのうちの少なくとも一つのスレッドが書き込み処理を行うことにより、データが不整合な状態になる現象のことです。

## データ競合を発生させてみる
以下のコードを実行すると、出力結果は1000にはなりません。
そして実行するたびに結果が異なります。

```swift
class Counter {
    var count = 0

    func increment() {
        count += 1
    }
}

let counter = Counter()

for _ in 0..<1000 {
    Task {
        counter.increment()
    }
}

Thread.sleep(forTimeInterval: 2)
print(counter.count)
```

# Sendableの必要性とは？
上記コードにおけるCounterクラスはデータ競合が発生するので、同時実行ドメイン間で安全に共有することができません。
にもかかわらず、コンパイルは問題なく通ってしまいます。

`Sendable`はこれを解決するために導入されました。
`Sendable`はデータ競合を発生させる可能性のあるコードをコンパイル時に検出することを可能にします。

# 問題を検出できるようにする
我々には`Sendable`対応のための猶予が与えられているので、現状では上記のようなコードを書いても警告も出ないしエラーにもなりません。
しかし、Swift6からはエラーになってコンパイルできなくなります。

いますぐに問題を検出できるようにするには、Swiftコンパイラにフラグを渡してあげます。

```swift
swiftSettings: [
    .unsafeFlags(["-Xfrontend", "-warn-concurrency"])
]
```

こうすると、該当箇所に警告が表示されるようになります。

![Untitled.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/81715/245db66b-f67e-41bd-8ef3-4828a0f35b3d.png)

> Capture of 'counter' with non-sendable type 'Counter' in a `@Sendable` closure

# Taskのクロージャは`@Sendable`
Task.initのシグネチャはこうなっていて、クロージャが`@Sendable`になっていることがわかります。

```swift
public init(priority: TaskPriority? = nil, operation: @escaping @Sendable () async throws -> Success)
```

# クロージャが`@Sendable`とはどういうことか？
関数またはクロージャに`@Sendable`をつけると、以下が適用されます。

- キャプチャする型は`Sendable`への準拠が必要
- 変数はキャプチャできない

前述のCounterクラスは非`Sendable`なため、1つ目の要件に合致せず警告が出ていたことがわかります。

`Sendable`な型であっても、クロージャ内で変数をキャプチャしている以下のコードはコンパイルエラーになります。
これについては-warn-concurrencyフラグをつけていなくてもエラーになります。

```swift
var count = 0
Task {
    print(count)  // Reference to captured var 'count' in concurrently-executing code
}
```

キャプチャリストを使用することで、変数の値を利用することができます。
以下のクロージャ内のcountはクロージャの外にあるcountとは別の新しい不変変数だからです。

```swift
var count = 0
Task { [count] in
    print(count)
}
```

# TCAでSendableを意識するポイント
ここまで理解すると、TCAのあの書き方の意味がわかります。

## EffectTask.task / run / fireAndForgetに渡すクロージャも`@Sendable`

.taskに渡すクロージャは`@Sendable`であり、stateはinoutで渡されるために可変であるため、クロージャ内でキャプチャすることができません。

そこで、stateの値をキャプチャリストでコピーし、クロージャ内で参照可能にしています。

```swift
func reduce(into state: inout State, action: Action) -> EffectTask<Action> {
    switch action {
    case .onAppear:
        return .task { [state] in
            await fetchResponse(TaskResult {
                try await repository.fetch(.init(newsId: state.newsId))
            })
        }
    }
}
```

## 依存のプロパティを`@Sendable`にしている理由

TCAで推奨されている依存（XxxClientとかXxxRepositoryとか）のインタフェース（プロパティ）はクロージャになっていて、`@Sendable`をつけています。

```swift
public struct XxxClient {
    public var fetch: @Sendable () async throws -> Entity
}
```

依存はEffectTask.taskなど並行コンテキストで利用されるため、データ競合を引き起こす可能性があります。

例えば、カウント値を保持し、increment()を呼ぶごとにカウントアップした値を返す機能を持つClientを考えます。

```swift
struct CountClient {
    var increment: () -> Int
}
```

このClientを以下のように実装するとしましょう。

```swift
extension CountClient {
    static func live() -> Self {
        var count = 0

        return .init(increment: {
            count += 1
            return count
        })
    }
}
```

Counterクラスの例と同様に以下のような処理を実装すると、データ競合が発生してしまいます。
最終的な出力は1001を期待したいですが、実際はそうなりません。

```swift
let client = CountClient.live()

for _ in 0..<1000 {
    Task {
        _ = client.increment()
    }
}

Thread.sleep(forTimeInterval: 2)
print(client.increment())
```

インタフェースに`@Sendable`をつけることで、データ競合が発生するコードを実装できないようにすることができます（-warn-concurrencyをつけている場合）。

具体的には、以下のようにコンパイルエラーになります。

```swift
struct Repository {
    var increment: @Sendable () -> Int
}

extension Repository {
    static func live() -> Self {
        var count = 0

        return .init(increment: {
            count += 1    // Mutation of captured var 'count' in concurrently-executing code
            return count  // Reference to captured var 'count' in concurrently-executing code
        })
    }
}
```

[TCAのドキュメント](https://pointfreeco.github.io/swift-composable-architecture/main/documentation/composablearchitecture/swiftconcurrency)で以下のように言及されています。

> This will restrict the kinds of closures that can be used when constructing FactClient values, thus making the entire FactClient sendable itself.

インタフェースを`@Sendable`にすると表現しましたが、DependencyValuesに登録するためにはClient自体を`Sendable`にする必要があります。

# 参考
https://www.pointfree.co/collections/concurrency/threads-queues-and-tasks/ep193-concurrency-s-future-sendable-and-actors

https://pointfreeco.github.io/swift-composable-architecture/main/documentation/composablearchitecture/swiftconcurrency

https://www.amazon.co.jp/o/ASIN/B0B76V1HNC

https://www.swiftlangjp.com/language-guide/concurrency.html#sendable-%E5%9E%8Bsendable-types