Sponsor Link
環境&対象
- 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
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 の仕組みを見てみました。
- 個別プロパティは、computed property 化してアクセス管理する
- 内部に、observationRegistrar を持ち、アクセス/ 変更が管理されている
- @Observable が付与された class は protocol Observable に conform させている
説明は以上です。
不明な点やおかしな点ありましたら、こちらまで。
SwiftUI おすすめ本
SwiftUI を理解するには、以下の本がおすすめです。
SwiftUI ViewMatery
SwiftUI で開発していくときに、ViewやLayoutのための適切なmodifierを探すのが大変です。
英語での説明になってしまいますが、以下の”SwiftUI Views Mastery Bundle”という本がビジュアル的に確認して探せるので、便利です。
英語ではありますが、1ページに コードと画面が並んでいるので、非常にわかりやすいです。
View に適用できる modifier もわかりやすく説明されているので、ビューの理解だけではなく、どのような装飾ができるかも簡単にわかります。
超便利です
販売元のページは、こちらです。
SwiftUI 徹底入門
# SwiftUI は、毎年大きく改善されていますので、少し古くなってしまいましたが、いまでも 定番本です。
Swift学習におすすめの本
詳解Swift
Swift の学習には、詳解 Swift という書籍が、おすすめです。
著者は、Swift の初期から書籍を出していますし、Swift の前に主力言語だった Objective-C という言語についても同様の書籍を出しています。
最新版を購入するのがおすすめです。
現時点では、上記の Swift 5 に対応した第5版が最新版です。
Sponsor Link