WWDC23:Discover Observation in SwiftUIの日本語訳です。WWDC23で追加・変更されたSwiftUIのObservation(状態監視)について解説しています。@State、@Environment、@Bindable、@Observableについて解説しています。
目次
What is Observation?(Observationとはなにか?)
Observation(状態監視)について紹介します。
この機能によって、標準的なSwiftの構文を使用してモデルを定義し、そのモデルへの変更にUIが反応するようにそれらの型を使用することができます。これにより、SwiftUIを使った開発はシームレスで直感的なものになります。
今日はいくつかのトピックを取り上げます。
Observationとはなにか?、SwiftUIからプロパティラッパーを使用する際の便利なルールのセット、そしてObservableのより高度な使い方をいくつか取り上げます。
@Observableについて
Observationは、プロパティの変更を追跡するためのSwiftの新機能です。それは、通常のSwiftの型で動作し、マクロのマジックでそれらを変換します。私たちはしばしばデータモデル型を書き、それらは最終的にSwiftUIで使いたい多くのプロパティを持っています。
@Observableを追加するだけで、データモデルの変更にUIが反応するようになります。Swift 5.9の新機能では、モデルをこれまで以上にシンプルに記述することができます。
@ObservableはSwiftの新しいマクロシステムを使う。@Observableは、Swiftコンパイラに、あなたが書いたコードから、型を観察できるように拡張された形に変換するように指示します。SwiftUIのビューをパワーアップするために、observable型を使用することができます。そして、驚くべき部分は、それらが動作するためにプロパティのラッパーの任意の並べ替えを必要としないということです。
私たちのドーナツ屋台のアプリからいくつかのおいしいサンプルがあります。ここではドーナツを表示するシンプルなビューがあります。
SwiftUI はボディコール(「var body」の箇所)を実行するときにモデルが特定のプロパティにアクセスすることを知っています。この場合、ドーナツメニュービューのbodyを実行するときにプロパティ’donuts’にアクセスすることを検出できます。bodyが実行されるとき、SwiftUIは’Observable’型から使用されるプロパティへのすべてのアクセスを追跡します。そして、その追跡情報を取得し、特定のインスタンス上でそれらのプロパティのいずれかに次の変更がいつ変更されるかを決定するために使用します。ここで、ドーナツ追加ボタンをクリックしてドーナツ配列を変更すると、ドーナツメニュー・ビューが無効になり、それに応じてUIが更新される。ビュー本体を実行するときに決定されたトラッキングプロパティにそのプロパティが含まれていないためです。
次に、computedプロパティを使用したケースについて説明します。コンピューテッド・プロパティの追加は、前述と同じルールに従います。使用されるプロパティが変更されると、UIが更新されます。
新しく追加されたコンテンツでは、モデルのorderCountが呼び出され、ordersプロパティにアクセスします。
つまり、この例では注文が変更されると、orderCountが注文のプロパティにアクセスするため、そのテキストが更新されることになる。@Observableマクロを使用すると、Observationをサポートできるように型を拡張します。これにより、SwiftUIはこれらのプロパティへのアクセスを追跡し、次のプロパティがObservationからいつ変更されるかを観察することができます。そのようなことを追跡することで、特定のプロパティが変更されたときだけUIがビューの本体を再計算することができます。
マクロを深く知りたい方は、「Write Swift macros」と「Expand on Swift macros」のセッションをぜひチェックしてみてください。
SwiftUI property wrappers(SwiftUI プロパティラッパー)
Observableによって、SwiftUIのプロパティラッパーはこれまで以上に簡単になりました。
@Stateについて
State、environment、bindableはSwiftUIで動作するための3つの主要なプロパティラッパーです。SwiftUIでobservable型とインターフェイスするためにプロパティラッパーが必要ないケースはすでに説明しましたが、必要なケースを考えてみましょう。まずは@Stateから。ビューがモデルに保存された独自の状態を持つ必要がある場合、@Stateプロパティを使います。
ここでは、observableモデルオブジェクトDonutがsheetプレゼンテーションで使用されています。sheetが表示されると、donutToAddステート変数が編集可能フィールドに値をバインドするために使用されます。「donutToAdd」プロパティは、それが含まれるビューのライフタイムによって管理されます。
@Environmentについて
次は、@Environmentです。Environmentは、値をグローバルにアクセス可能な値として伝搬させます。これにより、さまざまな場所で値を共有することができる。Observable型は、アクセスに基づいて更新が行われるため、ここでは非常に効果的です。FoodTruckMenuViewの本体を呼び出すとき、AccountオブジェクトのuserNameプロパティにアクセスします。userNameが変更されると、メニュービューが更新されます。
@Bindableについて
プロパティ・ラッパー・ファミリーの中で最も新しいものは@Bindableです。Bindableプロパティ・ラッパーはとても軽量です。その型からバインディングを作成できるようにするだけです。bindableでラップされたプロパティからバインディングを取得するのはとても簡単です。そのプロパティへのバインディングを取得するには$構文を使うだけです。
多くの場合、これはobservable型へのバインディングになる。DonutViewでは、名前をTextを利用しています.
しかし実際には、その名前を編集できるようにしたい。そこで、Textの代わりにTextFieldを使おう。TextFieldはバインディングを受け取ります。バインディングからTextFieldの値を読み込み、ユーザーが値を変更するとバインディングに書き戻します。Donutにバインディングを作るために必要なことは、Donutのプロパティで@Bindableプロパティラッパーを使うことです。
プロパティラッパーアノテーションは、$donut.name構文を使用することを可能にし、使用されたときにバインディングを作成します。
SwiftUI property wrappersの選び方
ラッパーをまとめると、SwiftUIでobservableモデルを使うために答える必要がある質問は3つだけです。
このモデルはビュー自体の状態である必要がありますか?もしそうなら、@Stateを使います。このモデルはアプリケーションのグローバル環境の一部である必要があるか?もしそうなら、@Environmentを使います。このモデルはバインディングが必要なだけですか?もしそうなら、新しい@Bindableを使う。これらの質問のどれにも答えがイエスでない場合は、モデルをビューのプロパティとして使用します。
Advanced uses(高度な使用方法)
ここまでは、ストアド・プロパティとしてモデルで始まるプロパティについて説明しました。Observableはもっと多くのことができます。SwiftUIはインスタンスごとにフィールドへのアクセスを追跡するので、配列、optional、またはそれに関して、observableモデルを含む任意の型を使用できることを意味します。
DonutListビューはDonutモデルの配列を持っている。それぞれのモデル自体は@Observableです。これらのDonutの名前のいずれかが変更されたとき、SwiftUIは特定のインスタンスでそのプロパティへのアクセスを検出し、ビューを無効にするタイミングを知るためにそれを追跡します。したがって、ここで、Donutの名前が「Randomize」ボタンを通して変更されるとき、ビューはそれに応じて更新されます。
これにより、好きなようにモデルを構築することができます。観測されるモデルの配列を持つこともできますし、他の観測可能なモデル型を含むモデル型を持つこともできます。Observableの一般的なルールは、使用されるプロパティが変更された場合、ビューは更新されます。このルールが完全に適用されない場合があります。計算されたプロパティが一緒に構成されるストアド・プロパティを持っていない場合、Observationで動作させるために2つの特別なステップを踏む必要があります。これは、Observationされるプロパティが、オブザーバブルタイプのストアドプロパティのある種のコンポジションによって変更されない場合にのみ必要です。この場合、プロパティがアクセスされた時とプロパティが変更された時にObservationに伝えるだけでよい。
これは、Observationが通常プロパティへのアクセスを合成する方法です。ただし、ここではこれらのカスタム・アクセス・ポイントを手動で書き換えて、非オブザーバブルの場所を読み取り、名前を保存できるようにしています。ほとんどの場合、このような手動によるケースは必要ありません。なぜなら、問題のモデルのプロパティは、他の格納されているプロパティから構成されているからです。しかし、そのような高度な機能が必要なまれなケースでは、Observationは十分に柔軟ですが、あなた自身で行うには十分簡単です。SwiftUIはそれらのプロパティへのアクセスによって観測可能な型を追跡するので、構成の変更を識別できます。
Computed propertiesについて
つまり、Computed(計算型)プロパティが他のストアド・プロパティから構成されている場合、Observationはそのまま機能します。しかし、そうでない数少ないケースでは、Observationを直接使用して、アクセスや変異のフラグを手動で追加することができます。
ObservableObjectとは
以前の Food Truck アプリでは、新しい @Observable マクロで行ったのと同じことを達成するために ObservableObject を使用しました。今日SwiftUIを使うアプリを持っている場合、非常に似た状況にあるかもしれません。
Observableマクロはコードを簡素化し、パフォーマンスも向上させることができます。変更前、FoodTruckModel型はObservableObjectプロトコルを持っており、@Publishedプロパティ・ラッパーでマークされたプロパティをいくつか持っていました。
Observableマクロへの変更はとても簡単です。必要なのは、ObservableObjectへの準拠を削除し、@Publishedを削除し、@Observableマクロでマークするだけだ。
ビューに関しては、多くの@ObservedObjectと@EnvironmentObjectプロパティ・ラッパーがあった。
ObservedObjectラッパーはすべて消滅したか、バインディングだけが必要になり、新しい@Bindableに変更された。EnvironmentObjectラッパーは@Environmentラッパーに変わった。ObservableObjectから新しい@Observableマクロへの変更は、ほとんどがアノテーションの削除だけだった。あるいは、@State、@Environment、@Bindableの3つの主要なプロパティ・ラッパーに単純化した。これにより、考慮すべき選択肢が少なくなり、新しい機能を書くのが簡単になった。
Harness the magic(魔法を利用する)
Observationはちょうどいいレベルのマジックだ。Observableマクロを使えば、データモデルを直接扱うことができる。必要であれば、高度なユースケースのためのマニュアルを書くこともできる。新規開発にはObservableを使うのが一番簡単だ。また、既存のアプリケーションでは、Observableを使うことで、モデルをシンプルにし、新しい機能を追加する際のパフォーマンスを向上させることができる。ぜひ試してみてほしい。