🐈

SwiftUIにおけるObservableObjectの管理

2021/07/25に公開

SwiftUIでデータを監視可能にするためにはObservableObjectプロトコルに準拠したオブジェクトを作成します。

class Book: ObservableObject {
    @Published var title = "Great Expectations"
}

ObservableObjectに準拠したオブジェクトをSwiftUIから監視対象とする方法は3つ用意されています。

StateObject

StateObjectはViewに対してインスタンスを1つだけ生成して管理する振る舞いをします。そのためアプリケーション全体で使用するようなデータ(モデル)の管理に向いています。

@main
struct BookReader: App {
    @StateObject var library = Library()

    var body: some Scene {
        WindowGroup {
            ...
        }
    }
}

ObservedObject

ObservedObjectはStateObjectとは異なりViewのライフサイクルに合わせてインスタンスが管理されます。ObservedObjectを使ってインスタンスを生成するとViewの再生成のタイミングでデータが消えてしまうといった副作用が起こる可能性があります。また、Viewを生成するたびにインスタンスも生成されるのでオーバーヘッドが大きくなることがあります。ObservedObjectではインスタンスの生成は行わずに上位のViewなどから渡されたインスタンスを参照する目的で使用するのが良いです。

struct LibraryView: View {
    @StateObject var book = Book()

    var body: some View {
        BookView(book: book)
    }
}

struct BookView: View {
    @ObservedObject var book: Book

    var body: some View {
        BookEditView(book: book)
    }
}

struct BookEditView: View {
    @ObservedObject var book: Book

    // ...
}

EnvironmentObject

EnvironmentObjectは階層が上位のViewからenvironmentObject()で渡されたインスタンスにアクセスできます。ObservedObjectとは異なり直接の親ではないViewからの受け取りが可能になるのでインスタンスのバケツリレーが不要です。アプリ全体で使用するオブジェクトを多数のViewや深い階層の子Viewに渡したい場合に便利です。

@main
struct BookReader: App {
    @StateObject var library = Library()

    var body: some Scene {
        WindowGroup {
            LibraryView()
                .environmentObject(library)
        }
    }
}

struct LibraryView: View {
    @EnvironmentObject var library: Library

    // ...
}

どのように使い分けると良いのか

インスタンスの生成はStateObjectで行い、インスタンスを参照するときにObservedObjectもしくはEnvironmentObjectを使うのが良いと思います。

  • App, Scene, View でインスタンスを生成するときは StateObject を使う
    • Viewのライフサイクルとは独立してオブジェクトを管理できる
  • 親View からインスタンスを受け取るときは ObservedObject を使う
    • Viewのライフサイクルによってオブジェクトの生成と破棄が行われる
  • environmentObjectによって渡されたときは EnvironmentObject を使う
    • アプリのトップレベルで宣言したインスタンスを全てのViewから参照できる

参考資料

Managing Model Data in Your App

https://developer.apple.com/videos/play/wwdc2020/10040/

Discussion