基本#
SwiftUI では、@ObservedObject と @StateObject はどちらもプロパティラッパーであり、ビュー内のオブジェクトの状態を管理するために使用されます。
@ObservedObject は、ObservableObject プロトコルのオブジェクトを現在のビューによって監視されるオブジェクトとしてマークします。つまり、@ObservableObject でマークされたオブジェクトの状態が変化すると、ビューは自動的に更新されます。通常、@ObservedObject はビュー間で状態を共有するために使用されます。
例えば、ユーザーの設定を保存するために ObservableObject プロトコルに準拠した UserSettings クラスがあるとします。
class UserSettings: ObservableObject {
@Published var theme: Theme = .light
}
次のように、ビュー内で @ObservedObject プロパティラッパーを使用して UserSettings オブジェクトを監視できます。
struct SettingsView: View {
@ObservedObject var userSettings = UserSettings()
var body: some View {
// ...
}
}
この例では、ユーザーが設定を変更すると、UserSettings の @Published プロパティが更新され、SettingsView が自動的に更新されます。
一方、@StateObject は、ObservableObject プロトコルに準拠したオブジェクトを現在のビューが所有する状態オブジェクトとしてマークします。つまり、@StateObject でマークされたオブジェクトが変更されると、それは所有するビューにのみ影響を与えます。通常、@StateObject は単一のビューで状態を管理するために使用されます。
例えば、タイマーの状態を管理するために ObservableObject に準拠した TimerModel クラスがあるとします。
class TimerModel: ObservableObject {
@Published var time: Double = 0
init() {
Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { _ in
self.time += 1
}
}
}
次のように、@StateObject を使用して TimerModel を所有する状態変数を作成できます。
struct TimerView: View {
@StateObject var timerModel = TimerModel()
var body: some View {
Text("\(timerModel.time)")
}
}
この例では、TimerModel の @Published プロパティが変更されると、TimerView のみが影響を受けます。他のビューは更新を受け取りません。
本当の違い#
前述の例では、あまり明確な違いはありません。では、なぜ @StateObject でラップされたオブジェクトは現在のビューにのみ影響を与えるのでしょうか?
実際には、上記の例ではあまり違いがありません。しかし、@ObservableObject のオブジェクトが親ビューによって 2 つのビューに渡される状況では、大きな違いが生じます。
ビュー内で @StateObject を使用してオブジェクトのインスタンスを作成すると、インスタンスはビューと同じライフサイクルを持つため、ビューが破棄されるとインスタンスも破棄されます。
そのため、@StateObject でラップされたオブジェクトは現在のビューにのみ存在し、現在のビューにのみ影響を与えます。他のビューが同じオブジェクトを必要とする場合は、@ObservedObject ラッパーを使用する必要があります。
上記の例では、@StateObject ラッパーを使用して TimerModel のインスタンスを作成し、timerModel というプロパティに格納しました。そのため、オブジェクト timerModel は TimerView 内にのみ存在し、他のビューからはアクセスできません。
以下は、ObservableObject のオブジェクトが複数のビューによって監視される例です。
class UserSettings: ObservableObject {
@Published var theme: Theme = .light
}
struct SettingsView: View {
@ObservedObject var userSettings: UserSettings
var body: some View {
VStack {
Text("Settings View")
Text("Current Theme: \(userSettings.theme.rawValue)")
}
}
}
struct ProfileView: View {
@ObservedObject var userSettings: UserSettings
var body: some View {
VStack {
Text("Profile View")
Text("Current Theme: \(userSettings.theme.rawValue)")
}
}
}
struct ContentView: View {
@StateObject var userSettings = UserSettings()
var body: some View {
VStack {
SettingsView(userSettings: userSettings)
ProfileView(userSettings: userSettings)
}
}
}
この例では、ユーザーが userSettings の theme プロパティを更新すると、SettingsView と ProfileView が自動的に更新され、最新のテーマが表示されます。これは、ObservableObject プロトコルの同じオブジェクトを複数のビューが監視する基本的な実装です。
SettingsView または ProfileView のいずれかで @StateObject を使用すると、2 つのビューに引数を渡し、それぞれが独自のコンテキストでコピーを作成するようなものになります。このような状況では、状態の変更は別々のコンテキストに制約され、互いに影響を与えません。
投稿カバーとして画像を共有
これは、昨日 Midjourney で Jaina のために生成したアバターです。彼女はとても気に入っています。