Open5

SwiftUI: @Stateの変数の更新内容が見えないケースがある

kabeyakabeya

SwiftUIで@Stateの変数が複数あるとします(例えばab)。

  1. ボタンタップのハンドラなどでその複数の変数(ab)を同時に更新する。
  2. .onChange(of: perform:)でそれらの変数のうちの1つ(例えばa)を監視する。
  3. その際、perform:のクロージャ内で監視対象でない変数(この例ではb)を参照する。

どういう条件か分かりませんが、3のタイミングで参照したbの値が、1を行う前の値のまま、というケースがあります。

kabeyakabeya

違いました。

.onChangeではなく.sheet(item: content:)でした。

ただ、どっちにしても小さいテストコードを書いても再現しません。

kabeyakabeya

色々試した結果、値が取れるようになった方法もいくつかありますが、原因が分からないのでこれで良いのかも分からない、という状況です。

具体的な方法は以下のようなものです。

  1. クロージャにキャプチャリストを追加して、bを明示的に指定する。
  2. aの更新時にTaskを使う。

キャプチャリストを追加するというのは以下のようにします。

.sheet(item: a) { [bValue = b] aValue in
   // aValueはaの新しい値。bValueはbの新しい値
}

Taskを使うのは、更新時に以下のようにします。

{
    b = // 何かの値
    Task {
        a = // 何かの値
    }
}

何かタイミング依存のような気はしているのですが…

kabeyakabeya

何かタイミング依存のような気はしているのですが…

クロージャが構築される時点でキャプチャされている、というのであれば、まったく更新されてない、というような動きになっていれば納得なのです。
更新されるケースもあるのがよく分かりません。
というか、軽いサンプルを作ると更新されてしまいます。

kabeyakabeya

今回は、abの更新と監視を分ける(同時に行わない)ように見直して、とりあえずは期待通りに動くようになりました。

こういうこと(同時に更新して片側を監視して、差異検出後に両方を参照)はしないほうがいいんでしょうかね。

Combineとかで変数の更新に伴う通知が行くようになっていて、それで通知を受けてビューの更新が走るんだけども、更新といいながら再度initされていて、@Stateinitのあとに前のビューの値から引き継がれる?ようなことなんでしょうか。引き継ぎのタイミング次第で、見えたり見えなかったり。

この辺、裏方のやっている詳細な動きがよく分からないので、想像ですけども。