質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.35%
Swift

Swiftは、アップルのiOSおよびOS Xのためのプログラミング言語で、Objective-CやObjective-C++と共存することが意図されています

Q&A

1回答

2793閲覧

SwiftUI @Stateでラップされているプロパティに変更があるとき、State構造体の内部ではどのようなことが起こっている?

sho_0807

総合スコア0

Swift

Swiftは、アップルのiOSおよびOS Xのためのプログラミング言語で、Objective-CやObjective-C++と共存することが意図されています

0グッド

2クリップ

投稿2020/11/11 09:30

編集2020/11/11 10:04

プロパティラッパである構造体Stateでは、計算型プロパティとして{get nonmutating set}が定義されていますが、セッターでは、nonmutatingが宣言されているので、State構造体の値自体に変更はありません。

プレイグラウンドで以下のように実験すると、変更がないことがわかります。

import SwiftUI

var state_i: State<Int> = State.init(wrappedValue: 3) //_value: 3, _location: nil

state_i.wrappedValue = 1 // _value: 3, _location: nil

しかし、例えば、ビューのボタンを押したときに、状態変数に新たな値が代入されるというアクションがあるとすれば、それは当然、状態変数の値が変わり、ビューにも反映されます。一体それはどうしてでしょうか。

何かしらのイベント(ユーザー操作など)が起こると、source of truthであるStateで状態が新たに更新されます。そして、body以下のビューにレンダリングを要求し、ビューの更新が行われるのだと思います。
しかし、State構造体では、値の更新が循環されるような仕組みが導入されていないように見えるのに、なぜ、上手く値が更新されるのでしょうか。

該当のソースコード

import SwiftUI

struct HelloView: View {

@State private var name: String = "" var body: some View { Button(action: {self.name = "Hello!" }) { //新たに値が代入されるが、Stateの値に変更はない Text(self.name)              //しかし、ビューにはきちんと反映する } .padding(10) }

}

補足情報(FW/ツールのバージョンなど)

環境
MacOS 10.15.7
Xcode 12.1

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答1

0

SwiftUIの内部の処理は公開されていないので推測するしかありませんが、
https://kateinoigakukun.hatenablog.com/entry/2019/06/08/232142

の記事が参考になると思います(SwiftUIのベータ版が発表された頃の古い記事です)。

wrappedValue プロパティの宣言に沿って定義するなら

swift

1struct State<Value> { 2 var storage = UserDefaults.standard 3 var wrappedValue: Value { 4 get { 5 return storage.object(forKey: "StateStorage") as! Value 6 } 7 nonmutating set { 8 storage.set(newValue, forKey: "StateStorage") 9 renderView() 10 } 11 } 12 13 init(wrappedValue value: Value) { 14 storage.set(value, forKey: "StateStorage") 15 } 16 17 func renderView() { 18 let value = storage.object(forKey: "StateStorage") as! Value 19 print("rendering (value)") 20 } 21}

上記のようなイメージになると思います。
(とても単純にデフォルメしたイメージです。)

wrappedValueを更新しているのではなく、Stateの中にストレージがあり、そのストレージを更新し、更新すると同時にレンダリング処理を呼び出しているイメージです。
(上記の例は、ストレージのわかりやすいイメージとしてUserDefaultsを使っていますが、もちろん本物のState構造体のstorageはUserDefaultsではなく、オンメモリで動作する高速なストレージだと思います。)

投稿2020/11/18 14:39

TakeOne

総合スコア6299

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

sho_0807

2020/11/19 12:51

ご回答ありがとうございます。 質問してから、少し考えてみたのですが、構造体Stateには、mutating func update(){ } が定義されています。わざわざmutatingが宣言されているということは、このメソッドによって、自身のプロパティ値を変更しているのではないのでしょうか。 nonmutating set と mutating func update() をどう組み合わしているのかわかりませんが。。。
sho_0807

2020/11/19 12:58

私も異なるクラスで定義されたストレージを更新し、getでアクセスすると考えましたが、質問のコードからわかる通り、更新とアクセスの場所が違います。一体どのように実装されているのか本当に気になります。。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

まだベストアンサーが選ばれていません

会員登録して回答してみよう

アカウントをお持ちの方は

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.35%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問