@ObservedObject への DI を onAppear で決して行わない。
#26
YusukeHosonuma
started this conversation in
Pending
Replies: 1 comment
-
|
話が変わるかもしれませんが、 struct SakatsuInputScreen: View {
@StateObject private var viewModel: SakatsuInputViewModel<SakatsuUserDefaultsClient>
init(sakatsu: Sakatsu?) {
self._viewModel = StateObject(wrappedValue: SakatsuInputViewModel(sakatsu: sakatsu ?? .init()))
}
}ViewModel に型パラメータがある場合、View 側で具象型を指定する必要があるのが気になりますが、、 |
Beta Was this translation helpful? Give feedback.
0 replies
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
-
Overview
SwiftUI において、
ObservableObjectに任意のオブジェクトを DI したいケースが存在する。例えば、以下のコードでは
@EnvironmentObject経由で取得したAuthStateをRootViewModelに引き渡そうとしている。しかし、コメントで記載しているとおり、上記のコードはコンパイルエラーとなる。
Initializer において
@EnvironmentObjectで宣言されたauthStateがプロパティが初期化されていない段階でアクセスしようとしているためだ。これは SwiftUI ではなく、Swift 言語における言語仕様からくる制限だ。では、以下のようなコードはどうだろうか?
これも本質的にはさきほどのコードと変わらず、初期化が完了していない時点で
authStateプロパティにアクセスしようとしているためコンパイルエラーとなる。では、
lazyを使用して初期化を遅延させたらどうだろう?これも残念ながらコンパイルエラーとなる。Property-wrapper に対して
lazyキーワードは使用できないためだ(そもそも Property-wrapper の時点で、ある種のlazyを実現しているとも言える)。それでは考え方を変えて、画面が表示された際に呼ばれる
onAppearのタイミングにおいて、メソッド経由で DI (method injection)する方法はどうだろうか。これはコンパイルこそパスするものの、絶対に避けるべき実装パターンである。
一見するとこれは正しく動作するし、実際に上記のコードを動かしてみても問題は見つけられない。
しかし、View の初期化タイミングと
onAppear()のタイミングは常に一致するわけではないため、実行時に以下のようなことが起こりうる。RootViewの再生成によってRootViewModelが再初期化される。onAppearが呼ばれず、RootViewModelのAuthStateにオブジェクトが設定されない。RootViewModelは不正な状態のまま機能し続けようとする。上記のコード例では
authStateを強制アンラップ型(AuthState!)で宣言しているため、内部的にauthStateにアクセスしようとしたタイミングでクラッシュを引き起こす。Optional型(
AuthState?)にすればクラッシュこそ避けられるものの、不正なステートのまま動作しているため、結局のところ期待しない動作をすることに代わりはない。こうした意図しない不正なステートを発生させないため、
@ObservedObjectへの DI をonAppearなどで決して行ってはならない。Discussion
この問題が発生する具体的なコードを忘れてしまったため、正確な記事を書けなくなってしまった;
以前、この問題を解決する最もシンプルな手段が
@ObservedObjectの代わりに@StateObjectを利用することだと考えたのだけれど、結局のところ不正なステートを一時的にしろ発生させるので、onAppearなどで DI を行うこと自体避けるべきパターンなのかもしれない。あるいは
onAppearで初期化されるまで、絶対にauthStateにアクセスしないように実装する手もあるかもしれないが、簡単にミスを引き起こしそうな気もしなくもない(Optionalにすればクラッシュは確実に防げるが...)。Beta Was this translation helpful? Give feedback.
All reactions