SwiftUIのProperty Wrapperをマスターする②〜クラス型につける〜
こんにちは。ママさんエンジニアのトモヨです。
再びProperty Wrapper(プロパティラッパー)について見解を深めていきます。
今回はクラスを子のクラスに引き継がせるときに使用する
@ObservedObject
@StateObject
@EnvironmentObject
の違いを学びます。実際に書いたコードはこちらです。
下記の記事を参考に学びました。とっても詳しく記載されていてわかりやすかったです。私の記事では気になった部分も含め解説していきます。
https://blog.personal-factory.com/2021/01/23/how-to-use-propertywrapper-in-swiftui/
ObservableObjectを準拠させたクラスを用いた参照型のデータオブジェクトの扱い
今まではcounter、textなど値や文字を用いていました。今回はオブジェクトを渡すにはどうやるの?ということです。まず渡すオブジェクトを作ってみましょう。
まずDataSourceというクラスにcounterを持たせてあげましょう
class DataSource {
var counter = 0
}
ObservableObjectを準拠させます。@Publishedをつけます。これでプロパティがSwiftUIで監視されます。
class DataSource: ObservableObject {
@Published var counter = 0
}
各クラスで値を生成するとき、@ObservedObjectや@StateObjectを付与します。この2つの違いはデータを破棄するときの違いです。
struct SwitchColorView: View {
@State private var isDanger: Bool = false
var body: some View {
VStack {
Button("Change the Color") {
isDanger.toggle()
}
if isDanger {
Circle().foregroundColor(.red)
.frame(width: 200, height: 200)
} else {
Circle().foregroundColor(.green)
.frame(width: 200, height: 200)
}
StateObjectCounterView()
ObservedObjcetCounterView()
Spacer()
}
}
}
struct StateObjectCounterView: View {
@StateObject private var dataSource = DataSource()
var body: some View {
VStack {
Button("increment counter") {
dataSource.counter += 1
}
Text("StateObject count: \(dataSource.counter)")
.font(.title)
}
}
}
struct ObservedObjcetCounterView: View {
@ObservedObject private var dataSource = DataSource()
var body: some View {
VStack {
Button("increment counter") {
dataSource.counter += 1
}
Text("ObservedObject count: \(dataSource.counter)")
.font(.title)
}
}
}
SwitchColorViewをプレビューしてみましょう。
それぞれのincrement counterをタップし、Change the Colorのボタンを押すとObservedObjectのカウントは0に戻ってしまいます
@StateObjectのデータオブジェクトのライフサイクルはViewが表示してから非表示してからですが、@ObservedObjectはbodyが更新されるまでだそうです。
SwitchColorViewのbodyが更新されたので@ObservedObjectのデータオブジェクトが破棄され、カウントが0に戻ってしまったのですね。
要はこのパターンの場合は@StateObjectを使用しなければなりません。
@ObservedObjectは親Viewが子Viewに渡す時に使用します。
// 親
struct ObservedObjectTestView: View {
@StateObject private var dataSource = DataSource()
var body: some View {
ObservedObjectTestChildView(dataSource: dataSource)
}
}
// 子
struct ObservedObjectTestChildView: View {
@ObservedObject var dataSource = DataSource()
var body: some View {
VStack {
Button("increment counter") {
dataSource.counter += 1
}
Text("ObservedObject count: \(dataSource.counter)")
.font(.title)
}
}
}
ここからは私個人の意見です。全部StateObjectでいいじゃん!って感じもしますが、それをしてしまうとずっとdataSourceを保持してしまいます。メモリ逼迫にもなるのでObservedObjectを使った方がいいです。あと、ObservedObjectって明示的に書いてあると、ここがデータの発生元じゃないなってわかります。なのでStateObject、ObservedObjectは使い分けた方がいいと思いました。
@EnvironmentObject
子のクラスにオブジェクトを渡す場合は@ObservedObject、@StateObjectを使用しますが、さらに下の子のクラスに渡す場合はどうなるでしょう
こちらの図のようにChildViewに一回オブジェクトを渡し、ChildViewはこのオブジェクトは知らなくていいはずなのに処理として書かないといけなくなってしまいました。ここで@EnvironmentObjectが登場します。
@EnvironmentObjectを使用すると、親Viewに.environmentObject修飾子をつければ、後は子孫Viewで使いたいViewだけに@EnvironmentObjectを付与することででデータオブジェクトにアクセスすることができます。
// EnvironmentObjectParentView.swift
import SwiftUI
// 親View
struct EnvironmentObjectParentView: View {
@StateObject private var dataSource = DataSource()
var body: some View {
EnvironmentObjectChildView().environmentObject(dataSource)
}
}
struct EnvironmentObjectParentView_Previews: PreviewProvider {
@StateObject static private var dataSource = DataSource()
static var previews: some View {
EnvironmentObjectParentView().environmentObject(dataSource)
}
}
// EnvironmentObjectChildView.swift
import SwiftUI
// 子view
struct EnvironmentObjectChildView: View {
var body: some View {
EnvironmentObjectGrandChildView()
}
}
struct EnvironmentObjectChildView_Previews: PreviewProvider {
static var previews: some View {
EnvironmentObjectChildView()
.environmentObject(DataSource())
}
}
// EnvironmentObjectGrandChildView.swift
import SwiftUI
// 孫view
struct EnvironmentObjectGrandChildView: View {
@EnvironmentObject var dataSource: DataSource
var body: some View {
Text("\(dataSource.counter)")
}
}
struct EnvironmentObjectGrandChildView_Previews: PreviewProvider {
static var previews: some View {
EnvironmentObjectGrandChildView().environmentObject(DataSource())
}
}
子viewのクラスはDataSourceを扱わず、孫のクラスにオブジェクトを渡すことができました。
まとめ
最初は使用用途に苦戦したProperty Wrapperですが、コードを書くことを通して理解を深めることができました。しっかり役割づけもされているのでわかりやすかったです。
この記事が気に入ったらサポートをしてみませんか?