63967


以下のコードは、`words1`という配列が`init`内で値が代入され、その後`for文`を使い、配列`words2`へ同じ要素を追加するものです。

ですが、`words2`に`@State`が付いていると`words2.count`のように要素の数を調べた時に0となっています。

どうして初期化処理が無効化されたかのようになっているのでしょうか？

[code]

    import SwiftUI
    
    struct hoge{
        var words1:[String]
        @State var words2:[String] = []
    
        init(){
            self.words1 = ["hello","world","programming","Swift"]
    
            for word in self.words1{
                self.words2.append(word)
            }
        }
    }
    
    var something = hoge()
    print(something.words2.count)
    
[/code]


CC BY-SA 4.0
2020-03-17T14:47:43.930
63943
2020-03-18T13:58:46.843
2020-03-18T01:07:07.887
3060
37250
question
swift swiftui
@State付きの変数の値の初期化が無効になる


結論を先に書くと、

**_`@State`を付けたプロパティは、イニシャライザの中で値を変更しても必ず無視されます。_**

* * *

残念ながら、`@State`の詳細についてきちんと解説した記事を見つけ出すことはできなかったのですが、Swift言語開発のコアメンバーでもあるJoe
Groff氏が書いている記事が見つかりました。

forums.swift.orgの@State messing with initializer flowというスレより:

<https://forums.swift.org/t/state-messing-with-initializer-flow/25276/3>

> Although that will compile, @State variables in SwiftUI should not be  
>  initialized from data you pass down through the initializer; since the  
>  model is maintained outside of the view, there is no guarantee that  
>  the value will really be used. The correct thing to do is to set your  
>  initial state values inline:
[code]

>     @State private var viewModel = SignInViewModel(...)
>  
[/code]

(抄訳)

> SwiftUIの`@State`変数はイニシャライザに渡された値から初期化しちゃいけない

思いっきり略してしまいましたが、ネットのあちこちを探して得た情報をまとめると、

  1. `@State`を付けた変数の実際の記憶領域は、SwiftUIによって割り当てられる
  2. イニシャライザの中では、それはまだ割り当てられていない  
(現在の実装では、その状態で更新しようとしてもエラーにもならず無視される。)

  3. 領域が割り当てられて正しく参照(更新も)できるのが保証されるのは`body`が呼ばれたとき

と言うわけで、`@State`を付けた変数は、最初にも書きましたが、

**_イニシャライザの中で値を変更しても必ず無視される_**

と言うことになります。

また、 ** _SwiftUIが管理していない型(要は`View`以外の型)では、イニシャライザの中以外でも使えません_**。

* * *

`@State`変数を固定値や簡単な式ではなく、何らかの処理の結果で初期化したいのであれば、例えばこんな風に書くことができます。

[code]

    import SwiftUI
    
    struct ContentView: View {
        var words1: [String]
        @State var words2: [String]
    
        init() {
            self.words1 = ["hello","world","programming","Swift"]
    
            var initWords2: [String] = []
            for word in self.words1 {
                initWords2.append(word)
            }
            _words2 = State(initialValue: initWords2)
        }
    
        var body: some View {
            VStack {
                Text("Hello, World!")
                //`body`内では`words2`に正しくアクセスできる
                List(words2, id: \.self) {word in
                    Text(word)
                }
            }
        }
    }
    
[/code]

以前[別スレ](https://ja.stackoverflow.com/a/62444/13972)で解説したように`@State`をつけた変数宣言は、実は`State<T>`(いまの場合`State<[String]>`)型の変数`_words2`なんてものを宣言していることになるので、その変数を`State.init(initialValue:)`と言うイニシャライザで初期化してやります。


CC BY-SA 4.0
2020-03-18T13:58:46.843
63967
2020-03-18T13:58:46.843
13972
63943
answer