SwiftUIでViewModelは@ObservedObjectではなく@StateObjectを使うべき
SwiftUIでアプリを開発する時に、アーキテクチャとしてMVVMを採用することがあるが、その際にViewModelは次のようにすれば簡単にデータバインディングできるが、@ObservedObjectではなく@StateObjectにした方がいい。
class ViewModel: ObservableObject {
@Published var count = 0
func add() {
count = count + 1
}
}
struct ContentView: View {
// ここは@StateObjectにした方がいい
@ObservedObject var viewModel = ViewModel()
var body: some View {
Text("Count: \\(viewModel.count)")
Button {
viewModel.add()
} label: {
Text("add")
}
}
}
なぜなら、@ObservedObjectのインスタンスはViewのライフサイクルに依存するので、親Viewが再描画されると、インスタンスが再生成されてしまいデータを保持できなくなるから。
@ObservedObject:Viewのライフサイクルに依存し、Viewの再描画でインスタンスは再生成される
@StateObject:Viewに対してインスタンスが1つだけ生成される
例えば、次のようにParentViewとChildViewの両方でカウンターがあり、ChildViewの方はChildViewModelがそのカウントを保持・更新できるとする。
ChildViewでカウントアップしてviewModel.add()を実行すると”Child count”は+1されていくが、ParentViewでカウントアップすると、ParentViewのbodyが再描画され、ChildViewも再描画されるためviewModelは初期化されてしまい、”Child count”は0に戻ってしまう。
struct ParentView: View {
@State var count = 0
var body: some View {
VStack {
Text("Parent count: \\(count)")
Button {
count += 1
} label: {
Text("add")
}
ChildView()
}.frame(width: 400, height: 400)
}
}
struct ChildView: View {
@ObservedObject var viewModel = ChildViewModel()
// @StateObject var viewModel = ChildViewModel()
var body: some View {
Text("Child count: \\(viewModel.count)")
Button {
viewModel.add()
} label: {
Text("add")
}
}
}
class ChildViewModel: ObservableObject {
@Published var count = 0
func add() {
count = count + 1
}
}
このように、View内でViewModelを保持する場合は@StateObjectを使用すべきである。一方で、親Viewから渡されるデータを参照するのみの場合は、再描画による初期化の影響を受けないので@ObservedObjectを使えばよい。
参考
この記事が気に入ったらサポートをしてみませんか?