[Swift][Observation] Observation の仕組み その1: Observable

     
⌛️ 2 min.

iOS17/ macOS14 で導入された Observation の仕組みを見ていきます。その1: Observable

環境&対象

以下の環境で動作確認を行なっています。

  • macOS15.0.1 Sequoia
  • Xcode 16.1 Beta
  • iOS 18.1
  • Swift 6

Observation 解説シリーズ記事

Observation を説明してます。

[Swift][Observation] Observation の仕組み その1: Observable [Swift][Observation] Observation の仕組み その2: withObservationTracking

Observable

Observable は、Observation フレームワークの一部として導入されました。


参考
ObservationApple Developer Documentation

なお、Observable という名称をもつものは、以下の2種類あります。

・macro
・protocol

順番に確認してみます。

protocol Observable

まずは、protocol の1つである Observable を見てみます。


参考
ObservableApple Developer Documentation

この protocol に conform するために必要な要件はありません。

この protocol に conform している型は、内部データが変更された時に 変更を通知する ということを表明しています。
このプロトコルは、いわゆる マーカープロトコル です。(マーカープロトコルの別の例としては、Sendable が有名です)

この後にでてくる macro の Observable を適用すると 適用した型が、protocol の Observable に conform するようになります。

macro Observable

macro である Observable を確認します。


参考
Observable()Apple Developer Documentation

MEMO

macro なので、Xcode 上で expand すればその実装は確認できます。
この記事では、expand されたコードを確認していきます。

macro Observable は、class にのみ適用できます。

class 以外に適用すると、エラーが発生します。(以下は struct )

@Observable    // '@Observable' cannot be applied to struct type 'MyStruct'
struct MyStruct {
    var value: Int = 3
}

なお、上記は struct での例ですが actor や enum に対しても同様のエラーが発生します。

以降では、class を使った以下のコードを使って確認してみます。

@Observable
class MyClass {
    var value: Int = 4
}

展開してみる

@Observable macro を展開してみると以下のようになります。

@Observable
class MyClass {
    @ObservationTracked
    var value: Int = 4
    @ObservationIgnored private let _$observationRegistrar = Observation.ObservationRegistrar()    

    internal nonisolated func access(
        keyPath: KeyPath
    ) {
        _$observationRegistrar.access(self, keyPath: keyPath)
    }

    internal nonisolated func withMutation(
        keyPath: KeyPath,
        _ mutation: () throws -> MutationResult
    ) rethrows -> MutationResult {
        try _$observationRegistrar.withMutation(of: self, keyPath: keyPath, mutation)
    }
}
extension MyClass: Observation.Observable {
}

いろいろと追加されていますが、大きくみると以下の3つです。
・MyClass のプロパティに、macro が追加されている
・MyClass に追加実装されている
・MyClass が Observable に conform している

以降では、下の方から順番に見ていきます。

Observable に conform させている箇所

macro で展開された 最後の2行 を確認すると、macro によって MyClass が Observable に conform することがわかります。

macro によって追加されるコードにより変更を通知するようになるので、Observable に conform させているということです。

追加実装されている箇所

以下のような追加実装がされていることもわかります。

class MyClass {
    //.. 省略 ..
    @ObservationIgnored private let _$observationRegistrar = Observation.ObservationRegistrar()    

    internal nonisolated func access(
        keyPath: KeyPath
    ) {
        _$observationRegistrar.access(self, keyPath: keyPath)
    }

    internal nonisolated func withMutation(
        keyPath: KeyPath,
        _ mutation: () throws -> MutationResult
    ) rethrows -> MutationResult {
        try _$observationRegistrar.withMutation(of: self, keyPath: keyPath, mutation)
    }
}
//.. 省略 ..

内部に、_$observationRegistrar というプロパティを定義しています。
そして、access / withMutation というメソッドを用意しています。

この access/ withMutation というメソッドは、どちらも 先に定義した _$observationRegistrar の持つ同名メソッドへ中継しているとわかります。

ObservationRegistrar は、データの変更を検知するための仕組み(ストレージ)を提供するものです。


参考
ObservationRegistrarApple Developer Documentation

ここでは、MyClass というクラスの value というプロパティについてのアクセスや変更を検知するための仕組みを提供してくれます。
この例では、MyClass には value という1つのプロパティしかありませんが、複数のプロパティが定義されていても追加される observationRegistrar は、1つです。 ObservationRegistrar は、1つあれば複数のプロパティへのアクセスや変更を検知できます。

@ObservationIgnored

よくみると、新しく追加されたプロパティには、@ObservationIgnored という macro が追加されていることがわかります。

この macro は文字通り 付与されたプロパティは、変更を検知/通知しない という意味です。

macro を展開しようとしても何も変わりません。
詳細は不明ですが、マーカープロトコルと同様な役割の マーカーマクロ(?) というべきもののようです。

macro が追加されたプロパティ

@Observable が展開されたコードをみると プロパティに @ObservationTracked が追加されていることがわかります。

この部分を展開してみると以下のようになります。

Observable
class MyClass {
    @ObservationTracked
    var value: Int = 4
    {
        @storageRestrictions(initializes: _value)
        init(initialValue) {
            _value = initialValue
        }
        get {
            access(keyPath: \.value)
            return _value
        }
        set {
            withMutation(keyPath: \.value) {
                _value = newValue
            }
        }
        _modify {
            access(keyPath: \.value)
            _$observationRegistrar.willSet(self, keyPath: \.value)
            defer {
                _$observationRegistrar.didSet(self, keyPath: \.value)
            }
            yield &_value
        }
    }
    @ObservationIgnored private var _value: Int = 4
    // ... 省略 ...
}

これは、ひとことでいうと、”value へのアクセスを _$observationRegistrar に知らせつつ、内部ストレージにアクセスする” というコードです。
実際のストレージは、_value になっています。_value は内部ストレージなので、@ObservationIgnored も付与されています。

@storageRestrictions は、computed property を使った init の仕組みの1つで、以下の記事で説明しています。
[Swift] computed property の init と storageRestrictions

get/set は、computed property の get/set そのままです。macro で用意した 関数を使って、アクセスや変更を observationRegistrar に通知しています。

_modify は、_(アンダーバー) 付き であることからもわかるように、公式には公開されていない機能で、プロパティの値を変更する時に フックできる機能です。

仕組み?

ここまで見てきたことで、@Observable の仕組みは、以下のように考えられます。

・プロパティを computed property に置き換え、アクセス/ 変更を検知する
・検知したアクセス/ 変更は、ObservationRegistrar で管理されている

ObservationRegistrar については、Apple の ドキュメントは、例によってシンプルな説明ですが、github で 実装を見ることができます。
github は こちら

ObservationRegistrar の仕組みは、別の記事で説明するかもしれません。

まとめ

@Observable の仕組みを見てみました。

@ Observable の仕組み
  • 個別プロパティは、computed property 化してアクセス管理する
  • 内部に、observationRegistrar を持ち、アクセス/ 変更が管理されている
  • @Observable が付与された class は protocol Observable に conform させている

説明は以上です。
不明な点やおかしな点ありましたら、こちらまで。

SwiftUI おすすめ本

SwiftUI を理解するには、以下の本がおすすめです。

SwiftUI ViewMatery

SwiftUI で開発していくときに、ViewやLayoutのための適切なmodifierを探すのが大変です。
英語での説明になってしまいますが、以下の”SwiftUI Views Mastery Bundle”という本がビジュアル的に確認して探せるので、便利です。

英語ではありますが、1ページに コードと画面が並んでいるので、非常にわかりやすいです。

View に適用できる modifier もわかりやすく説明されているので、ビューの理解だけではなく、どのような装飾ができるかも簡単にわかります。

超便利です

SwiftUIViewsMastery

販売元のページは、こちらです。

SwiftUI 徹底入門

# SwiftUI は、毎年大きく改善されていますので、少し古くなってしまいましたが、いまでも 定番本です。

Swift学習におすすめの本

詳解Swift

Swift の学習には、詳解 Swift という書籍が、おすすめです。

著者は、Swift の初期から書籍を出していますし、Swift の前に主力言語だった Objective-C という言語についても同様の書籍を出しています。

最新版を購入するのがおすすめです。

現時点では、上記の Swift 5 に対応した第5版が最新版です。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です