iOS17以降から使えるObservationについて

## はじめに
iOS17以降から`Observation`という新たな状態監視のFrameworkが使用できるようになりました。
しかし、状態監視のFrameworkとしてはすでに`Combine`が存在しています。
今回は新たに登場した`Observation`と`Combine`を比較しながら、`Observation`について見ていきます。

## `Observation`について
まずは、`Observation`と`Combine`の書き方を比較していきます。
今回はボタンをタップすると表示されている数字がインクリメントしていくViewをそれぞれ作成して比較していきます。

### `Combine`による状態監視
`CounterViewState`は`ObservableObject`を実装し、`@Published`を付与したプロパティの値を監視しています。
`CounterView`は`StateObject`を使って`CounterViewState`を監視可能なオブジェクトとしてインスタンス化することで、`CounterViewState`の状態が変化した際にViewを再描画します。

```swift:CounterViewState.swift
import Combine

final class CounterViewState: ObservableObject {
    @Published private(set) var count = 0

    func increment() {
        count += 1
    }
}

```

```swift:CounterView.swift
import SwiftUI

struct CounterView: View {
    @StateObject private var state: CounterViewState = .init()

    var body: some View {
        VStack {
            Text(state.count.description)
            Button("increment", action: {
                state.increment()
            })
        }
        .padding()
    }
}
```

### `Observation`による状態監視
`@Observable`はマクロで実装されている機能です。
`@Observable`付与することで状態の変化を監視するようコードを変換してくれます。
`Combine`と違い、`@Published`をプロパティに付与しなくとも値を監視してくれます。
`CounterView`は`@State`を使って`CounterViewState`の値を監視、更新時にViewを再描画します。

```swift:CounterViewState.swift
import Observation

@Observable final class CounterViewState {
    private(set) var count = 0

    func increment() {
        count += 1
    }
}

```

```swift:CounterView.swift
import SwiftUI

struct CounterView: View {
    @State private var state: CounterViewState = .init()

    var body: some View {
        VStack {
            Text(state.count.description)
            Button("increment", action: {
                state.increment()
            })
        }
        .padding()
    }
}
```

### `Observation`と`Combine`の違いについて
それぞれの実装を見てきました。
では実際には、`Observation`と`Combine`にはどういった違いがあるかを見ていきたいと思います。
`Observation`と`Combine`には大きく二つの違いがあります。

**監視対象のプロパティの定義方法の違い**
`Combine`では監視対象のプロパティには`@Published`をつける必要があります。
`Observation`では必要なプロパティ一つ一つに`@Published`をつけるといった冗長な作業は必要なく、通常通り定義するだけで問題ありません。
`@Published`をプロパティ一つ一つにつけていくのは冗長だったので、付与する必要がなくなったのは単純に嬉しいですね。

**オブジェクトの監視対象の違い**
`ObservableObject`を監視する場合、プロパティ毎の個別の変更を監視しているわけではなく`ObservableObject`の`objectWillChange` Publisherを監視しています。
`objectWillChange`は`ObservableObject`のいずれかのプロパティが変更された際に発火します。
なので、Viewの描画に必要のないプロパティが更新された場合でも、不必要に再描画が走ってしまう等の可能性があります。

`Observation` では、各プロパティを個別に監視しているため、関係のない値の変更によって不要な更新が走るといったことはありません。

## `Observation`で追加されたマクロ
`Observation`には`@Observable`の他に2つのマクロが追加されており、`Observable`のプロパティに対してアノテーションとして付与することができます。

### `@ObservationTracked`
`@ObservationTracked`をプロパティに付与することで、プロパティの変更を検知するようコードを変換します。
が、基本的に開発者が`@ObservationTracked`を使うことはありません。
`@Observable`マクロによって、対象のプロパティを`@ObservationTracked`を付与した形にコードを変換してくれるため、開発者が`@ObservationTracked`を意識することはないでしょう。

### `@ObservationIgnored`
`@ObservationIgnored`をプロパティに付与することで、該当のプロパティを監視しないようコードを変換します。
`Observation`では、`Combine`のように`@Published`を記載しなくてもプロパティの監視ができるようになっている反面、デフォルトで全てのプロパティを監視するようになっているので、監視の必要がない値には`@ObservationIgnored`をつけてあげる必要があります。

```swift:CounterViewState.swift
import Observation

@Observable final class CounterViewState {
    private(set) var count = 0 // 監視する
    @ObservationIgnored private(set) var title = "" // 監視しない
}

```

## 最後に
`Combine`よりも`Observation`の違いを見てきましたが、やはり`Observation`の方が使いやすそうだなといった印象です。
ただ、`Observation`はiOS17以降でしか使えないため、既存のアプリに対しては即導入ということはできないので今後に期待です！

また、何かご意見等ありましたらコメントいただけますと幸いです。
どうぞよろしくお願いいたします。


## 参考
- [swift-evolution/proposals/0395-observability.md at main · apple/swift-evolution](https://github.com/apple/swift-evolution/blob/main/proposals/0395-observability.md)