[SwiftUI][Combine] ObservableObject/@Published の落とし穴 (継承に注意)

ObservableObject, @Published を使用していて期待通りに動かないケースを説明します。
MEMO
この記事で説明しているケースは、macOS Big Sur 11.3 Beta 2 で修正された点のようです。(未確認)

環境&対象

以下の環境で動作確認を行なっています。

  • macOS Big Sur 11.2.2
  • Xcode 12.4
  • iOS 14.4

ObservableObject と @Published

ObservableObject に準拠したクラスのプロパティに @Published と付与します。

ビューの側では、@ObservedObject や @StateObject で扱うと、変更が入った時に、ビューが更新されます。

例えば、以下のコードでは、ボタンを押すと、画面が更新され、変更された値が表示されます。

Example

DirectConformClass
DirectConformClass

ObservableObject, @Published, @StateObject の組み合わせで、値が変化した時に画面更新が行われます。

継承したクラスで プロパティに @Published を付与した時

モデルは、クラスなので、継承するときもあるかもしれません。継承したクラスで @Published を使用すると問題が発生します。

継承したクラスのプロパティに @Published を付与しても、値更新時に 画面更新がなされません。

Example
InheritedConformClass
InheritedConformClass

InheritedConformClass も ObservableObject に 親クラスの DirectConformClass 経由で準拠しているはずですが、@Published を付与したプロパティの変更に対して画面更新がなされません。

ObservableObject は、子孫クラスに対して有効でない!?

設計意図は分かりませんが、ObservableObject に準拠したクラスを親クラスに持っていても、@Published は、有効とならないようです。

回避策

子クラスに改めて、ObservableObject 準拠を指定すると、親クラスですでに準拠しているというエラーとなります。

一番分かりやすい回避策は、該当プロパティを親クラスへ移動してしまうことです。

ただ、設計意図もあり、子クラスに持たせていると思いますので、移動が難しいことも多いはずです。
そのような時の回避策が以下です。

objectWillChange で個別に通知

親クラスは、ObservableObject に準拠しているため、子クラスからも objectWillChange を使うことができます。

ですので、子クラス側で以下のように定義することで、変更時の画面更新が行われます。

回避策

WorkAround
WorkAround

@Published は意味ないです

コードを見てわかる通り、indirectProperty の値変更検知は、willSet で行われているので @Published を付与している意味は無いです。

以下のことを想定して、付与しておくと良い気がします。

  • 将来的に、子クラスの @Published も 変更通知対象としてくれた時に向けた備忘録
  • 意図として、変更監視対象であることの明示

まとめ:親クラスが準拠している ObservableObject は、子クラスの @Published を更新通知対象としない

親クラスが準拠している ObservableObject は、子クラスの @Published を更新通知対象としない
  • 親クラスが ObservableObject に準拠していても、子クラスでの @Published 付きのプロパティは、変更監視対象とならない
  • 回避策1:該当プロパティを 親クラスへ移動する
  • 回避策2:該当プロパティの willSet で objectWillChange.send() を使い、自ら変更通知する

子クラスの @Published が無視される理由をご存知でしたら、教えてください _o_

説明は以上です。
不明な点やおかしな点ありましたら、こちらまで。

コメントを残す

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