SwiftUIのデータ管理についてのお勉強〜ObservableObject編〜

# 本記事について

SwiftUIのデータ管理についてのアウトプットやメモの共有です。
本文に載っているコードは、説明用であり、コピペでは動かないためご容赦ください。

## 全体像の一部

今回は、参照型のデータオブジェクトを扱う場合のProperty Wrapperについて説明します。

まず全体像としては、以下の通りです。（説明部以外は省略しています。）

<img width="567" alt="スクショ" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/477704/26fc132c-7f73-f816-1461-7d381f278394.png">

参照型のデータを扱う際、以下3つのProperty Wrapperを利用することができます。

- `@StateObject`
- `@ObservedObject`
- `@EnvironmentObject`

これらを利用するには、まずObservableObjectプロトコルの準拠が必要です。

```HomeViewModel.swift
final class HomeViewModel: ObservableObject {
    @Published var taskList: [TaskEntity] = [初期値]
}
```

このようにObservableObjectプロトコルを準拠した際、クラス内のプロパティに`@Published`を付与することで、`taskList`を監視することができます。

続いて、ObservableObjectを準拠したクラスをもとに、参照型データの扱い方について説明します。

### `@StateObject`について

全体像でも記載している通り、「参照型のデータオブジェクト、View自身でデータオブジェクトを保持する」の場合は、@StateObjectを利用します。（ちなみに、iOS14からです）

以下に、サンプルコードを示します。

```HomeView.Swift
struct HomeView: View {
    @StateObject private var viewModel = HomeViewModel()

    // レイアウト描画
    var body: some View {
        Button(action: {
             viewModel.taskList.append(TaskEntity(taskName: "皿洗い", taskTime: 1))
         }, label: {
             Text("追加")
         })
    //省略
    }
}
```

このサンプルコードの場合、viewModelが監視されているため、Buttonを押した際に、viewModelのtaskListが追加されるという実装になっています。
本来であれば、明示的な更新処理を入れる必要がありますが、SwiftUIのため必要ありません。

### `@ObservedObject`について

「参照型のデータオブジェクト、親Viewから渡されるデータオブジェクトを保持する」の場合は、`@ObservedObject`を利用します。

以下に、サンプルコードを示します。

```TaskSettingView.swift
final class DataSource: ObservableObject {
    @Published var taskList: [TaskEntity] = [初期値]
}

struct ParentView: View {
    @StateObject private var dataSource = DataSource()

    // レイアウト描画
    var body: some View {
        ChildView(dataSource: dataSource)
    }
}

struct ChildView: View {
    @ObservedObject var dataSource: DataSource // インスタンス生成は避ける
    
    var body: some View {
        VStack {
            HStack {
                Button(action: {
                    dataSource.taskList.append(TaskEntity(taskName: taskName, taskTime: 3))
                }) {
                    Text("add")
                }
            }
            .padding()
        }
    }
}
```

このように、ParentViewで生成したdataSourceをChildViewに渡します。Childで渡されたdataSourceは`ObservedObject`で宣言しています。そのため、dataSourceが監視されている状態になります。

### `@EnvironmentObject`について

「参照型のデータオブジェクト、アプリ全体から渡されるデータオブジェクトを保持する」の場合は、`@ObservedObject`を利用します。

例えば、親Viewから一番下の階層のViewにデータを渡そうとした際、`@EnvironmentObject`を利用することで階層を飛び越えてデータの監視が可能になります。

<img width="467" alt="スクショ" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/477704/ee6ca21c-4bfc-a81f-1fc4-c4b9f7d69524.png">

以下に、サンプルコードを示します。

```sample.swift
struct ParentView: View {
    var body: some View {
        ChiledView()
    }
}

struct ChiledView: View {
    var body: some View {
        GrandChiledView()
    }
}

struct GrandChiledView: View {
    @EnvironmentObject var dataSource: DataSource
    
    var body: some View {
        VStack {
            Button("add") {
                dataSource.taskList = TaskEntity(値の代入)
            }
            Text("Data: \(dataSource.taskList)")
        }
    }
}

struct GrandChildView_Previews: PreviewProvider {
    @StateObject static var dataSource = DataSource()
    static var previews: some View {
        ParentView().environmentObject(dataSource)
    }
}
```

親Viewに.environmentObject修飾子でデータをセットすることで、どの階層でもデータを監視することが可能となります。

## 終わりに

今回は以下の記事を参考に学習しました。
https://blog.personal-factory.com/2020/11/14/publish-swiftui-catalog-book/

ぜひ、参考にしてみてください。