[SwiftUI] ObservableObject, @ObservedObject, @Published を理解する

SwiftUI

     
⌛️ 2 min.
@State と同様によく使われる ObservableObject, @ObservedObject, @Published を説明します。

@State は struct にしか使えない

@State は、struct (というか、value-type) にしか使えないんです。

見てみます。

example code


struct IntValueStruct {   // (1)
  var intValue:Int = 0
}

struct ContentView: View {
  @State private var stateValue:IntValueStruct = IntValueStruct()
  var body: some View {
    VStack {
      Spacer()
      Text("stored value: \(stateValue.intValue)")
      Stepper("value \(stateValue.intValue)", value: $stateValue.intValue)
      Button(action: {print("\(stateValue.intValue)")}, label: {Text("check stateValue")})
      Spacer()
    }
    .padding()

  }
}

(1) で、@State の対象は、struct として定義しています。

StateWorksWell

[@State は、struct と組み合わせるとうまく動きます。]
Text で表示される値と、Stepper で変更する値が同期することが確認できます。

class に変えてみます。

example code


class IntValueClass { // (1) class に変えました
  var intValue:Int = 0
}

struct ContentView: View {
  @State private var stateClassValue:IntValueClass = IntValueClass()
  var body: some View {
    VStack {
      Spacer()
      Text("stored value: \(stateClassValue.intValue)")
      Stepper("value \(stateClassValue.intValue)", value: $stateClassValue.intValue)
      Button(action: {print("\(stateClassValue.intValue)")}, label: {Text("check Value")})
      Spacer()
    }
    .padding()

  }
}

@State は、class に適用するとうまく動かない

[@State は、class に適用するとうまく動かない]
class に変えただけなのですが、値が変更されなくなります。実際には、値は変更されているのですが、変更されたことによるビューの更新がされなくなります。

“Check Value” ボタンでその時点での値を、Xcode のコンソールに出力することができます。出力してみると Stepper の + ボタンを押した数分増えた数値が、コンソールに表示されることが確認できます。ですので、値が変更されていないのではなく、ビューが更新されていないと確認できます。

つまり、@State は、class に対して使用すると、@Binding と組み合わせることで、値のリファレンスを渡すことはできていますが、その変更をフェッチし、ビューを更新することができていない ことがわかります。

まさしく、このケースが、@ObservedObject を使うケースとなります。

@ObservedObject

@ObservedObject の property wrapper を付与するためには、class が ObservableObject を conform していなければなりません。

@ObservedObject を使ったものを追記すると以下のようになります。

example code


class IntValueClass :ObservableObject { // ObservableObject に 準拠
  @Published var intValue:Int = 0       // @Published で変更を検知するプロパティを設定
}

struct ContentView: View {
  @ObservedObject var obsClassValue:IntValueClass = IntValueClass()
  var body: some View {
    VStack {
      Spacer()
      Text("stored value: \(obsClassValue.intValue)")
      Stepper("value \(obsClassValue.intValue)", value: $obsClassValue.intValue)
      Spacer()
    }
    .padding()

  }
}

実行してもらうとわかりますが、下側の UI は、期待通りに動作します。

注意 (2022.1.12 追記)

StateObject 導入前は上記のコードでよかったのですが、Reference タイプのオブジェクトのライフサイクルをきちんと管理する StateObject の導入後は、上記コードの @ObservedObject を StateObject に読み替えてください。
この View 内で実体化したオブジェクトは、@StateObject を使用することで適切に管理してくれます。@ObservedObject は、外部で管理されていることを想定する property wrapper です。

ObservableObject は class とうまく動作する

「ObservableObject は class とうまく動作する」

ObservableObject とは? @Published とは?

ObservableObject とは、内部にobjectWillChange というプロパティを持つことを要求する Protocol です。objectWillChange のタイプは、Publisher です。

Apple のドキュメントは、こちら

@Published は、そのプロパティの変更について、上記の Publisher を使って、通知します。

@Published の projectedValue は、@Binding

なお、@Published が付与されたプロパティの $ を渡すと、@Binding になります。

まとめ

@State と @ObserervedObject
設定対象
@State struct, enum
@ObserervedObject class
ObservableObject と @Published
  • @ObservedObject として使用される class は、ObservableObject プロトコルに準拠するように定義されなければいけない
  • 変更を監視するプロパティに、@Published を付与する
  • @ObservedObject に含まれる @Published が付与されているプロパティは、@Binding で受ける

説明は以上です。
不明な点やおかしな点ありましたら、ご連絡いただけるとありがたいです。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です