Mekal Z

Mekal Z

A programmer running out of the wall.
twitter

SwiftUI: @ObservedObjectと@StateObjectの違い

基本#

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 のために生成したアバターです。彼女はとても気に入っています。

linguatale_appicon.png

読み込み中...
文章は、創作者によって署名され、ブロックチェーンに安全に保存されています。