はじめに
@StateObject
と @ObservedObject
の注意すべき点を知ったので、この機会に他の主要な Property Wrapper も含めて備忘録としてまとめたいと思います。
■ 値型のデータ
@State
- その View にデータを保持
- 外から渡されるデータではないので
private
が良さそう
@Binding
- その View で、親 View に保持されているデータを更新する場合に使う
struct SuperView: View {
@State private var value = 0
var body: some View {
SubView(value: self.$value)
}
}
struct SubView: View {
@Binding var value: Int
var body: some View {
// SuperView の value を更新できる
}
}
子 View がデータを更新しないなら、 @Binding
をつけず普通に子 View に渡しておけば、親 View 側でデータが更新された際には、再描画によって更新後の値が渡された)子 View が新しく作り直されるのでオッケーということですね。
■ 参照型のデータ
以下、データの型は ObservableObject
プロトコルへの準拠が必要。@Published
を付与したデータが更新されるタイミングで SwiftUI へ通知される。
@StateObject
- その View で参照型のデータオブジェクトを保持する場合に使用
- 外から渡されるデータではないので
private
が良さそう - オブジェクトのインスタンスは View の表示中に一度しか作成されない
- View インスタンスが破棄された後もオブジェクトは破棄されず SwiftUI から保持される(詳細は後述)
@ObservedObject
- 親 View から渡されるオブジェクトを扱う場合に使用(するといい)
- View インスタンスが破棄されるとオブジェクトも破棄される(詳細は後述)
@StateObject
と @ObservedObject
の注意すべき違い
struct MainView: View {
@State private var isHoge: Bool = false
var body: some View {
SubView() // isHoge が更新されると再描画される
}
}
このとき、 isHoge
が更新されたときに SubView()
が再描画されることによって、
@ObservedObject
の場合は、 hogeDataSource
はリセットされる
struct SubView: View {
// @ObservedObject で保持しているデータの場合
@ObservedObject private var hogeDataSource = HogeDataSource()
var body: some View {
// ...
}
}
@StateObject
の場合は、 hogeDataSource
はリセットされない
struct SubView: View {
// @StateObject で保持しているデータの場合
@StateObject private var hogeDataSource = HogeDataSource()
var body: some View {
// ...
}
}
SubView
のインスタンス自体は一度破棄されているけど、SwiftUI の機能によってオブジェクトが保持されている(先祖の View の再描画によるインスタンスの破棄からオブジェクトだけは保管して守られ、作り直されたインスタンスに付け直す)ということでしょうか。こちらはなんとなく直感に反する感じがしました… 覚えておきます。
@EnvironmentObject
- これで定義したオブジェクトは、すべての子孫の View からアクセスできる
struct MainView: View {
@StateObject private var hogeDataSource = HogeDataSource()
var body: some View {
// .environmentObject で渡す
ParentView().environmentObject(isHoge)
}
}
// 親子間で hogeDataSource の受け渡しをしていかなくても
struct ParentView: View {
var body: some View {
ChildView()
}
}
struct ChildView: View {
var body: some View {
GrandChildView()
}
}
// 子孫のどこからでも hogeDataSource にアクセスできる
struct GrandChildView: View {
// @EnvironmentObject でアクセスする
@EnvironmentObject var hogeDataSource: HogeDataSource
var body: some View {
// ...
}
}
データを保持している View から深い階層の View からアクセスさせる必要がある場合、1階層ずつ @ObservedObject
を渡すと冗長なので、 @EnvironmentObject
が有効ということですね。
■ 環境値
@Environment
-
View の環境値を読み取るために使う
-
例
@Environment(\.isPresented) var isPresented @Environment(\.dismiss) var dismiss
具体的にはスクリーンの情報、言語や地域、アプリの起動状態などについての値を読み取るときに使うものですね。こちらは 公式リファレンス を参照すると具体例が浮かんでわかりやすいと思います。