🧞

TCAでのStateの初期化はinitでデフォルト引数を使うと便利

2023/03/09に公開

はじめに

TheComposableArchitectureでのiOSアプリ開発についてアドバイスをする仕事をしていると、「Stateの初期化はどうするんだっぴ?」という質問を頻繁に受けるので書いておきます。

結論: initのデフォルト引数で初期化のパラメータを与えると便利

参考にisowordsでのState初期化例を示します

https://github.com/pointfreeco/isowords/blob/main/Sources/AppFeature/AppView.swift#L13-L27

利用時は下記

AppReducer.State()
// もしくはたいてい.init()でいい

たいてい利用時は型を推論できて省略できるので.init()でいい。

struct AppView_Previews: PreviewProvider {
    static var previews: some View {
      AppView(
        store: .init(
          initialState: .init(), // これ!
          reducer: AppReducer()
        )
      )
    }

理由

  • SwiftUIプレビュー時にパラメータを変化させたい
    • initでパラメータを指定できるようにすればいい
  • Stateのinitは書く回数が多く、Stateのプロパティは仕様の変更や設計の方針変更によって増減しやすい
    • 変更のたびにStateのinit呼び出し側を変更したくない
      • デフォルト引数を使えばいい

あんまりよくない例

あえて変な例を見せます

自身で初期化しない例

あえて面倒な例ですが、次のようにやると呼び出し側が初期化する必要があってさらに引数も省略されていないのですっごい面倒です。

public struct AppReducer: ReducerProtocol {
  public struct State: Equatable {
    public var game: Game.State?
    public var onboarding: Onboarding.State?
    public var home: Home.State

    public init(
      game: Game.State?,
      home: Home.State,
      onboarding: Onboarding.State?
    ) {
      self.game = game
      self.home = home
      self.onboarding = onboarding
    }

initで初期化するのにプロパティの宣言時に値をセットするのは無駄だし冗長

プロパティの宣言時に代入してさらにinitでデフォルト値を設定する例を示してみる。

public struct AppReducer: ReducerProtocol {
  public struct State: Equatable {
    public var game: Game.State? = nil // initでやってるので無駄
    public var onboarding: Onboarding.State? = .init() // これも無駄というか...
    public var home: Home.State = .init() // 無駄

    public init(
      game: Game.State? = nil,
      home: Home.State = .init(),
      onboarding: Onboarding.State? = nil
    ) {
      self.game = game
      self.home = home
      self.onboarding = onboarding
    }

無駄でもあるし、さらに問題になるのがそもそも設計としてこのAppReducerのStateが初期化されるとき、結局Onboading.Stateプロパティの値はnilになるべきなのに初期値で.init()してしまっているので無駄っていうかコストがかかって冗長です。

initで初期化しない例

TCAのTicTackToeというサンプルですがこんなのがあります。これをやるとプレビュー時にinitでプロパティの値を変更するパターンを試すのがちょっとだけ億劫です。

public struct Login: ReducerProtocol, Sendable {
  public struct State: Equatable {
    public var alert: AlertState<Action>?
    public var email = ""
    public var isFormValid = false
    public var isLoginRequestInFlight = false
    public var password = ""
    public var twoFactor: TwoFactor.State?

    public init() {}
  }

TCAはサンプルコードが充実してますが、それはベストプラクティスではない場合もあります。

Discussion