Compose の安定性

Compose では、型は安定または不安定のいずれかであると見なされます。タイプが安定しているということは、不変であるか、再コンポーズの間に値が変化したかどうかを Compose が把握できるということです。Compose が再コンポーズの間に値が変化したかどうかを把握できない場合、タイプは不安定です。

Compose は、コンポーズ可能な関数のパラメータの安定性を使用して、入力の比較方法を決定し、再コンポーズ中にコンポーズ可能な関数をスキップできるかどうかを判断します(Kotlin 2.0.20 以降では、強力なスキップ モードがデフォルトで有効になっています)。

  • 安定したパラメータ: Compose は、構造的等価性(Object.equals())を使用して安定したパラメータを比較します。コンポーザブルの安定したパラメータが以前の値と等しい場合、Compose はそれをスキップします。
  • 不安定なパラメータ: 強いスキップモードが有効になっている場合(Kotlin 2.0.20 以降ではデフォルト)、Compose はインスタンスの等価性(===)を使用して不安定なパラメータを比較し、同じオブジェクト インスタンスが渡された場合はコンポーザブルをスキップします。Kotlin 2.0.20 より前のバージョンでは(または強いスキップモードが無効になっている場合)、親が再コンポーズされると、Compose は常に不安定なパラメータを持つコンポーザブルを再コンポーズします。

アプリで不安定なパラメータの新しいインスタンスが頻繁に割り当てられる場合や、モデルに過剰なアノテーションを付加することで大きなコレクションに対してコストの高い .equals() チェックが強制的に実行される場合は、不要な再コンポーズや比較オーバーヘッドが発生する可能性があります。

このドキュメントでは、Compose が安定性をどのように判断するか、また、パフォーマンスと全体的なユーザー エクスペリエンスを向上させるために安定性を最適化する方法について詳しく説明します。

不変オブジェクト

次のスニペットは、安定性と再コンポーズの背後にある一般的な原則を示しています。

Contact クラスは不変のデータクラスです。これは、すべてのパラメータが val キーワードで定義されたプリミティブであるためです。Contact のインスタンスを作成すると、オブジェクトのプロパティの値を変更できなくなります。そうすると、新しいオブジェクトが作成されます。

data class Contact(val name: String, val number: String)

ContactRow コンポーザブルには Contact 型のパラメータがあります。

@Composable
fun ContactRow(contact: Contact, modifier: Modifier = Modifier) {
   var selected by remember { mutableStateOf(false) }

   Row(modifier) {
      ContactDetails(contact)
      ToggleButton(selected, onToggled = { selected = !selected })
   }
}

ユーザーが切り替えボタンをクリックして selected 状態が変化したときに何が起こるかを考えてみましょう。

  1. Compose は、ContactRow 内のコードを再コンポーズする必要があるかどうかを評価します。
  2. ContactDetails の唯一の引数が Contact 型であることを確認します。
  3. Contact は不変のデータクラスであるため、Compose は ContactDetails の引数が変更されていないことを確信できます。
  4. そのため、Compose は ContactDetails をスキップし、再コンポーズしません。
  5. 一方、ToggleButton の引数が変更されたため、Compose はそのコンポーネントを再コンポーズします。

可変オブジェクト

上記の例では不変オブジェクトを使用していますが、可変オブジェクトを作成することもできます。次のスニペットについて考えてみましょう。

data class Contact(var name: String, var number: String)

Contact の各パラメータが var になったため、クラスは変更できなくなりました。プロパティが変更されても、Compose はそれを認識しません。これは、Compose が Compose の State オブジェクトの変更のみを追跡するためです。

Compose は、このようなクラスを不安定と見なします。強いスキップモード(Kotlin 2.0.20 以降)では、Compose はインスタンスの等価性(===)を使用して Contact を比較します。つまり、contact.name をインプレースで変更しても再コンポーズはトリガーされませんが、同じ値を持つ新しく割り当てられた Contact インスタンスを渡すと、ContactDetails は強制的に再コンポーズされます(強いスキップモードがない場合、selected が変更されるたびに ContactDetails が再コンポーズされます)。

Compose での実装

再コンポーズ中にスキップする関数を Compose がどのように決定するかを検討することは、重要ではありませんが、役に立ちます。

Compose コンパイラがコードで実行されると、各関数と型に複数のタグのいずれかが付けられます。これらのタグは、再コンポーズ中に Compose が関数や型を処理する方法を反映しています。

関数

Compose は、関数を skippable または restartable としてマークできます。関数は、これらのいずれか、両方、またはどちらでもないとしてマークされることがあります。

  • スキップ可能: コンパイラがコンポーザブルをスキップ可能としてマークした場合、すべての引数が以前の値と等しい場合、Compose は再コンポーズ中にそのコンポーザブルをスキップできます。(Kotlin 2.0.20 以降の強いスキップモードでは、再起動可能なコンポーザブルはすべて自動的にスキップ可能になります)。
  • 再起動可能: 再起動可能なコンポーザブルは、再コンポーズを開始できる「スコープ」として機能します。つまり、この関数は、状態が変化した後に Compose が再コンポーズのコードの再実行を開始できるエントリ ポイントになります。

型

Compose は、マークの型を immutable または stable としてコンポーズします。各タイプは次のいずれかです。

  • 変更不可: プロパティの値が変更されることがなく、すべてのメソッドが参照透過的である場合、Compose は型を不変としてマークします。
    • すべてのプリミティブ型は不変としてマークされます。これには、String、Int、Float が含まれます。
  • Stable: 構築後にプロパティを変更できる型を示します。実行時にこれらのプロパティが変更されると、Compose はその変更を認識します。

デバッグの安定性

パラメータが変更されていないコンポーザブルをアプリが再コンポーズしている場合は、まず、不安定な型の新しいインスタンスが再コンポーズごとに割り当てられているかどうか(または、強いスキップモードが無効になっている場合は、コンポーザブルに不安定な型の var プロパティまたは val プロパティを持つパラメータがあるかどうか)を確認します。

Compose の安定性に関する複雑な問題を診断する方法の詳細については、安定性をデバッグするガイドをご覧ください。

安定性の問題を修正する

Compose 実装の安定性を高める方法については、安定性の問題を修正するガイドをご覧ください。

概要

全体として、次の点に注意する必要があります。

  • パラメータ: Compose は、コンポーザブルの各パラメータの安定性を判断し、再コンポーズ中に構造の等価性(.equals())またはインスタンスの等価性(===)を使用して比較するかどうかを決定します。
  • すぐに修正する: コンポーザブルがスキップされず、パフォーマンスの問題が発生している場合は、不安定なパラメータの新しいインスタンスが各パスで再作成されているか、State の代わりに var プロパティが使用されているかを確認します。
  • コンパイラ レポート: コンパイラ レポートを使用して、クラスについて推測される安定性を判断できます。
  • コレクション: Compose は、標準のコレクション インターフェース(List、Set、Map)を不安定と見なします。これは、基盤となる実装が変更可能である可能性があるためです。強いスキップモード(Kotlin 2.0.20 以降でデフォルトで有効)では、コレクション参照が変更されない場合、不安定なコレクションは高速な O(1) インスタンスの等価性(===)を使用して再コンポーズをスキップします。構造的等価性(O(N) .equals())が特に必要な場合を除き、大規模なコレクションを Kotlinx の不変コレクションに変換したり、コレクションを保持するクラスに @Immutable または @Stable のアノテーションを付けたりすることは避けてください。再コンポーズごとにすべての要素を比較すると、再コンポーズ自体よりもコストが高くなる可能性があります。
  • その他のモジュール: Compose コンパイラが実行されないモジュールのクラスは、不安定と見なされます(強いスキップモードで === を使用して比較)。データソースが、同じ値を持つ非 Compose モジュールから小さなフラット モデルを頻繁に再インスタンス化する場合は、安定性構成ファイルを構成するか、.equals() が安価な @Stable または @Immutable を使用できます。

関連情報