TCAの@Sharedを使ってStateを共有する

# 親と子の間Stateを共有する

長い間TCAを使っている方は親と子の間Stateを共有したいシチュエーションに遭遇したと思います。自分の方でも少し整理したので、この記事書くことにしました。間違いがありましたら遠慮なくコメントで指摘お願いします！

## 親と子の間でStateを共有

これは一番シンプルな使い方ですね：

```swift
@Reducer
struct ParentFeature {
  @ObservableState
  struct State {
    @Shared var count: Int           // 1
    var child: ChildFeature.State
    // Other properties
  }
  // ...
}
```

```swift
@Reducer
struct ChildFeature {
  @ObservableState
  struct State {
    @Shared var count: Int          // 2
    // Other properties
  }
  // ...
}
```

```swift
case .presentButtonTapped:
  state.child = ChildFeature.State(count: state.$count)  // 3
  // ...
```

1. ParentFeatureの中で@Sharedのcountを定義します
1. ChildFeatureも同じcountを定義します
1. ParentFeatureが持つ子のFeature（この場合はchild）を作成する時、$countを使ってParentFeatureのcountの参照をChildFeatureに渡す事ができます。

ChildFeatureのcountを変更するとParentFeatureのcountも変更され、もちろんParentFeatureのcountを変更するとChildFeatureのcountも変更されます（当たり前ですね、参照渡してますので）


## アプリの何処でもstateを取れるにしたい(永続性あり)

上の例は参照を子に渡していますが、場合によっては渡さなくてもstateをシェアした、下の例をみましょう：

```swift
@Reducer
struct ChildFeature {
  @ObservableState
  struct State {
    @Shared(.inMemory("count")) var count = 0 // 1
    // Other properties
  }
  // ...
}
```

```swift
case .onAppear:
  return .publisher {      // 2
    state.$count.publisher
      .map(Action.countUpdated)
  }


case .countUpdated(let count):
  // Do something with count
  return .none
```

```swift
case .onAppear:
  return .publisher {
    state.$count.publisher
      .map(Action.countUpdated)
  }


case .countUpdated(let count):
  state.count = count + 1 // 3 ❌
  return .none
```

1. default値を設定する必要があり、最初の値とします。ここの例は `.inMemory`使っていますが、TCAは`.appStorage` と `.fileStorage`複数のパターン用意されてます。`.appStorage`は`UserDefault`内のデータを取り、`.fileStorage`は指定したファイル内のデータを取ります。色々用意されてますね
1. ChildFeatureがcountの変更を受けたい場合、`.publisher`を使って`count`を観測でき、そして呼び起こす`Action`も指定できます。`.map`なので`Type`が同じであれば指定できる
1. この場合は無限ループが発生するので要注意ですね（Feature内`.publisher`と`@Shared`は同時に入れるのをやめましょう）

これで参照をわざわざ渡さなくても、stateをシェアできる

# ＠Shared stateの初期値

まず＠Sharedの定義を見てみると：

```swift
/// A property wrapper type that shares a value with multiple parts of an application.
///
/// See the <doc:SharingState> article for more detailed information on how to use this property
/// wrapper.
@dynamicMemberLookup
@propertyWrapper
public struct Shared<Value: Sendable>: Sendable {
  private let reference: any Reference
  private let keyPath: _SendableAnyKeyPath

  init(reference: any Reference, keyPath: _SendableAnyKeyPath) {
    self.reference = reference
    self.keyPath = keyPath
  }
  ...
```
```swift
protocol Reference<Value>: AnyObject, CustomStringConvertible, Sendable {
  associatedtype Value: Sendable
  var value: Value { get set }

  func access()
  func withMutation<T>(_ mutation: () throws -> T) rethrows -> T
  #if canImport(Combine)
    var publisher: AnyPublisher<Value, Never> { get }
  #endif
}
```

`Shared`内は`reference: Reference`で定義されてますね、そして`Reference`の中に`value`が見つかりました。`Reference`は`AnyObject`ですね！
となると`Shared`の内部は実際`Reference`の参照を持っていて、`Reference`の中に本当の`value`が存在しています。


# ＠Shared state初期化する
通常Stateの初期化メソッドは我々で定義する必要はなく、自動生成された`init`を使えばいいのですが、＠Shared含めたStateには自動生成された`init`では不十分です。その原因は＠Sharedのstateは実際value typeではなく、参照型です。親が持ってた子供の状態は、親からあげたのか？それとも子供自身が作れた状態を分けたいですね。なのでcustom initで明白にする必要があります。



## valueの根本は親からの場合

```swift
public struct State {
  @Shared public var count: Int
  // other fields


  public init(count: Shared<Int>, /* other fields */) { // 1
    self._count = count
    // other assignments
  }
}
```

1. この場合custom initは親の参照で初期化します

## valueの初期値は自分からの場合

```swift
public struct State {
  @Shared public var count: Int
  // other fields


  public init(count: Int, /* other fields */) { // 1
    self._count = Shared(count)                 // 2
    // other assignments
  }
}
```

1. この場合custom initは本当のvalue（Int）で初期化します
1. そして初期化メソッド内で `Shared`を作成します

# 永続性ありの＠Shared初期化

```swift
public struct State {
  @Shared public var count: Int                                     // 2
  // other fields


  public init(count: Int, /* other fields */) {                     // 1
    self._count = Shared(wrappedValue: count, .appStorage("count")) // 3
    // other assignments
  }
}
```

1. 永続性ありの＠Sharedのcustom initも本当のValue（Int）を渡します。
1. 既にお気づきになられたと思いますが、ここの `.appStorage`は省略可能です、永続性設定は初期化メソッドで指定しましたので（参照だからですかね :smile: ）
1. custom init内で永続性ありの＠Shared初期化する場合は `wrappedValue:`がある初期化メソッドを使用する必要があります。実際ここのcountは`@autoclosure () -> Int`にする方がわかりやすくなると思います。以下の例を考えましょう：
    1. `.appStorage`内は実際`count`がありました（例えば前回アプリ起動した時値が保存された）
    1. 当然、初期化メソッドが渡された`count`を使わずに、`.appStorage`内の値を使って`Shared`を作成するのが正しい

なので以下の書き方もできます：
```swift
public struct State {
  @Shared public var count: Int
  // other fields


  public init(count: @autoclosure () -> Int, /* other fields */) {
    self._count = Shared(wrappedValue: count(), .appStorage("count"))
    // other assignments
  }
}
```

# まとめ


Shared state（永続性ありのパターン）を使う時の手順：
1. Feature内共有したいstateを`＠Shared`を付ける
1. Stateのcustom initを追加（適切なcustom initを作りましょう）
1. stateを取りたいFeature内に `.publisher`を追加して値を取る

これくらい分かれば大体問題無いと思いますが、他にも`＠SharedReader`とか、`withLock` とか知る必要がある物あります、どちらも理解しやすいと思います。

参考：
https://pointfreeco.github.io/swift-composable-architecture/main/documentation/composablearchitecture/sharingstate/#app-top