WIP: Inside Flutter の翻訳

英語の勉強がてら、[Inside Flutter](https://flutter.io/docs/resources/inside-flutter)を翻訳していきます。
おかしなところが多々あると思うので、そういった部分の指摘は**コメントではなく編集リクエストでお願いします**。

また、ドキュメントの翻訳に加え、実際のコードで役に立ちそうな部分を括弧内に記入しました。

# Inside Flutter
このドキュメントでは、FlutterのAPIを可能にするFlutterのツールキットの内部的な仕組みについて説明します。
Flutterの`Widget`達は積極的にコンポジションを用いて作られているので、Flutterで構築されたユーザーインターフェイスには沢山の`Widget`が含まれています。この仕事量をサポートするため、Flutterのデータ構造は木構造となっており、それが多くの定数化に基づいた最適化を持つように、レイアウトと`Widget`構築の為に準線形アルゴリズムを用いています。

いくつかの詳細を加え、このデザインはまた、ユーザーが見ることのできる`Widget`を沢山生成するコールバックを用いる、無限スクロールリストを、デベロッパーが簡単に作れるようになります。


# 積極的なコンポジション
Flutterの特徴的な側面の一つとして、積極的なコンポジションが挙げられます。
`Widget`は他の`Widget`を組み合わせることによって作られ、それら自体は、徐々に基本的な`Widget`から構築されています。例えば、`Padding`は他の`Widget`のプロパティとしてではなく、`Widget`として作られています。結果、Flutterによって作られたユーザインターフェイスは、沢山の`Widget`から構成されています。


`Widget`構成の再帰は`RenderObjectWidget`で終わります。これは、基礎となる`Render`ツリーのノードを作る`Widget`です。`Render`ツリーはユーザインターフェイスのジオメトリ(形状)を保存するデータ構造で、これはレイアウト処理時に算出され、描画時とヒットテスト時に使われます。ほとんどのFlutterのデベロッパーは直接`RenderObject`を扱うようなことはなく、`Widget`を用いて`Render`ツリーを操作します。

(RenderObjectWidget.createRenderObject -> RenderObject)

`Widget`層での積極的なコンポジションをサポートするため、Flutterは`Widget`層と`Render`ツリー層の両方で、多くの効率的なアルゴリズムと最適化を用います。これについては次の小節で説明します。


## 準線形なレイアウト処理
多くの`Widget`と`RenderObject`について、良いパフォーマンスを得る為の鍵となるのが効率的なアルゴリズムです。最も重要なのはレイアウト処理のパフォーマンスで、 これは`RenderObject`のジオメトリ(例えば、サイズとか位置とか)　を決めるアルゴリズムです。幾つかの他のツールキットはO(N^2)以上の計算量となるレイアウト処理アルゴリズムを用いています(たとえば、制約領域における固定点のイテレーションとか)。

(class _SemanticsGeometry)

Flutterは 初期化時のレイアウト処理では線形のパフォーマンス、そしてそれに続く更新時には準線形のパフォーマンスとなることに焦点を当てています。通常、レイアウト処理にかけられる時間は`RenderObject`の数よりももっと遅くなります。

Flutterは毎フレームにレイアウト処理をし、レイアウト処理アルゴリズムは一方向に走査します。
親がそれらの子孫のレイアウト処理メソッドを呼び出すことによって、制約(Constraint)が木構造の下の方に渡されていきます。子孫は再帰的に彼らのレイアウト処理を行い、そしてレイアウト処理メソッドの戻り値によってジオメトリを木構造の上の方に返していきます。重要なのは、一度`RenderObject`がレイアウト処理メソッドから返されたら、その`RenderObject`は次のフレームのレイアウト処理まで参照されない、ということです。この方法は、他の方法では測定とレイアウトの受け渡しが別々になっていたのを、一つにまとめます。その結果、各`RenderObject`は二回参照されます。一回目は木構造を降りるとき、2回目は木構造を上がるときです。

(RenderObject.layout(Constraints) -> void,
 Geometryを返すメソッドは分からず…)

Flutterはこの一般的な手順のため、いくつか特化した部分があります。最も共通的な特化部は`RenderBox`で、これは二次元デカルト座標で動作するものです。ボックスレイアウトでは、制約は縦幅の最小値と最大値、横幅の最小値と最大値になります。レイアウト処理時には、子要素はこの制約の領域の中でサイズを選択し、ジオメトリを決定します。子要素のレイアウト処理の後、親要素はその制約システムに基づいて子要素の位置を決めます。子要素の位置は子要素のレイアウト処理が終わるまで決まらないので、子要素はその位置に依存することはできないことに注意してください。結果的に、親要素は子要素のレイアウトを計算せずに自由に子要素の位置を変えられるようになります。
より一般的に、レイアウト処理の間、親要素から子要素に送られる唯一の情報は制約で、子要素から親要素に送られる唯一の情報はジオメトリとなります。これらの不変性はレイアウト処理の負担を減らすことができます。:

- もし子要素が、そのレイアウトが`dirty`でないとマークされていたら、親要素が子要素に、以前と同じ制約を与える限りは、その子要素はすぐにレイアウト処理の走査から切り離せます

(See RenderObject
..markNeedsLayout 
..markParentNeedsLayout
..markNeedsLayoutForSizedByParentChange
..markNeedsCompositingBitsUpdate
..markNeedsPaint
Elementと違い、_dirtyフラグはない。
)


- 親が子のレイアウトメソッドを呼び出すたびに、親要素は子要素から返されたサイズ情報を使用するかどうかを示します。 よくあることですが、親要素がサイズ情報を使用しない場合、新しいサイズが既存の制約に準拠することが保証されるため、子要素が新しいサイズを選択した場合、親要素はレイアウトを再計算する必要はありません。

- `Tight`制約は、厳密に一つのジオメトリが満たすことのできる制約です。例えば、横幅の最小値と最大値、縦幅の最小値と最大値が互いに等しい場合、これらの制約を唯一満たすことができるサイズは、それと同じ縦幅と横幅を持つ場合のみです。もし親要素が`Tight`制約を指定する場合、親要素は　
それが子要素のサイズに依存しない限りは、
子要素がそのレイアウトを再計算する場合でも、親要素はそのレイアウトを再計算する必要が無くなります。
なぜなら、子要素は親要素から新しい制約が渡されない限り、そのサイズを変更できないからです。

(Constraints.isTight -> bool)

- `RenderObject`は、それが親要素から渡された制約を使うことをそのジオメトリを決定するためだけに宣言できます 。こういった宣言は、たとえ制約が`Tight`でなくても、親要素のレイアウトが子要素のサイズに依存しようとしても、そのレンダーオブジェクトの親要素がそのレイアウトを再計算する必要がなくなることをフレームワークに知らせます。
なぜなら、子要素は親要素から新しい制約が渡されない限り、そのサイズを変更できないからです。
これらの最適化の結果として、レンダーオブジェクトツリーが`dirty`なノードを含んでいる場合、これらのノードとその周りのサブツリーの、限られた部分がレイアウト処理時に走査されます。

## 準線形のウィジェット構築
レイアウト処理のアルゴリズムに似て、Flutterの`Widget`構築アルゴリズムは準線形です。構築後、`Widget`達はユーザインターフェイスの論理構造を保持する、`Element`ツリーに保持されます。

(Widget.createElement -> Element)


この`Element`ツリーは、`Widget`自身は不変、つまり彼らは彼らの親要素や子要素の関係を記録できないので、不可欠のものとなります。
`Element`ツリーはまた、`Stateful`ウィジェットに関連付けられた`State`オブジェクトを保持します

(StatefulElement._state -> State, State._element -> StatefulElement)

ユーザからのインプットに応答するため、例えば、開発者が`State`オブジェクトにて`setState`を呼び出した場合、`Element`は`dirty`になることがあります。
フレームワークは`dirty`な`Element`のリストを保持し、ビルド時にそれらに直接ジャンプして`clean`な`Element`をスキップします。
ビルド時には、情報は一方向に`Element`ツリーを下り、これは各`Element`はビルド時に少なくとも一回参照されることを意味します。
一度クリーンにされると、帰納法により、それらの全ての祖先の`Element`も`clean`にされるので、`Element`は再び`dirty`になることはできません。

(Element
..makeNeedsBuild -> void
.._dirty -> bool
..rebuild -> void,
BuildOwner
.._dirtyElements -> List<Element>
..buildScope -> void
)

`Widget`は不変なので、`Element`が`dirty`であるとマークされていなかった場合、もし親要素が同一のウィジェットを用いて`Element`をリビルドした時は、`Element`はビルド時の走査からすぐに抜け出すことができます。そのうえで、`Element`が行えばいいのは、新しい`Widget`と古い`Widget`が同一であることを確立する為の、二つの`Widget`の同一性の検証のみです。
開発者達は再投影パターンを実装するためにこの最適化を活用します。
この再投影パターンでは、前のビルド時に`Widget`にメンバー変数として格納された、プリビルドされた子`Widget`を用います。

(StatefulElement.update の `assert(widget == newWidget)`)


ビルド時、Flutterは`InheritedWidget`を使うことによって親のチェーンの走査を避けます。もし`Widget`が同じようにして親を走査した場合、例えばテーマカラーを決める処理の場合、ビルド処理が木構造の深さに応じてO(N^2)となり、積極的なコンポジションによって多くの計算量を要してしまうからです。この親の走査を避ける為、フレームワークは各`Element`の`InheritedWidget`のハッシュテーブルを維持することによって、情報を木構造の下に流します。通常、多くの`Element`が同じハッシュテーブルを参照するので、新しい`InheritedWidget`を作る`Element`のみがこのハッシュテーブルを変更します。

## 線形的な和解(?)

人気の信念に反して、Flutterは木構造差分アルゴリズムを採用しません。(Reactとかにあるやつ)
その代わり、フレームワークはO(N)のアルゴリズムで`Element`の子要素リストを個別に調べて、`Element`を再利用するかどうか判断します。
子要素の線形的な和解(?)アルゴリズムは、以下のケースで最適化されます:

- 古い子要素リストが空の場合
- 二つの子要素リストが同一である場合
- 子要素リストの一部のみで、複数の`Widget`の挿入、削除があった場合
- もし各リストが同じ`Key`を持つ`Widget`をもつ場合、その二つの`Widget`は同一である


この手法は、実行時の型情報と各`Widget`の`Key`を比較することによって、子要素リストのはじめと終わりを合わせる為にとられました。この手法ではすべての不一致の子を含む各リストの中央に空でない範囲を見つける可能性があります。(?????)
フレームワークはそれから　 子要素をその範囲に `Key`に基づいて古い子要素をハッシュテーブルに置きます。(?????)

次に、フレームワークはこの新しい子要素リストの範囲を走査し、そしてハッシュテーブルに`Key`がマッチするものを問合せます。マッチしなかった子要素は捨てられ、一から再構築されます。一方、マッチした子要素は新しい`Widget`と共に再構築されます。

## 木構造の操作
`Element`を再利用することはパフォーマンス改善に重要です。`Element`は`StatefulWidget`の為の`State`と、基礎となる`RenderObject`の二つの重要なデータを持つからです。
フレームワークが`Element`を再利用できる場合、 ユーザインターフェイスの論理的な部分の`State`は保存され、前に計算されたレイアウト情報が再利用でき、全部のサブツリーの走査を避けます。事実、`Element`を再利用することは、Flutterが`State`とレイアウト情報を保存する`non-local`なツリーの変異をサポートする上で重要です。

(non-local treeって何…？)

開発者達は、`GlobalKey`と`Widget`を紐づけることにより、`non-local`ツリーの変異を実行できます。これらの`GlobalKey`はアプリケーション全体でユニークなものとなり、それはスレッド特有のハッシュテーブルに登録されます。ビルド時には、開発者は`Widget`を`GlobalKey`とともに、`Element`ツリーの任意の場所に移動できます。その場所で新しい`Element`を構築するより、フレームワークはハッシュテーブルをチェックし、既にある`Element`を前の場所から新しい場所に再配置し、サブツリー全体を保存します。

(GlobalKey._registry)

再配置されたサブツリー内の`RenderObject`は、レイアウト制約のみがレンダーツリーにて親から子に渡される情報のため、そのレイアウト情報を保存できます。新しい親要素は、その子要素のリストが変更されるため、`dirty`であるとマークされます。しかし、もし以前と同じレイアウト制約が子要素に渡された場合、子要素はすぐにレイアウト処理の走査から抜け出せます。

`GlobalKey`と`non-local`ツリーの変異は、`Hero`遷移やナビゲーションなどで開発者達に広く用いられています。

## 定数化に基づく最適化

これらのアルゴリズムの最適化に加え、積極的なコンポジションを達成する為に、いくつかの重要な定数化に基づく最適化が鍵となってきます。これらの最適化は、説明してきた主要なアルゴリズムに最も重要なものとなります。 

- 子モデルの不可知論
子要素のリストを用いる、ほかの多くのツールキットとは違い、Flutterの`Render`ツリーは特定の子要素の実装に依存しません。例えば、`RenderBox`classには、具体的な子要素を表す`firstChild``nextSibling`のようなインターフェイスではなく、abstractな`visitChildren()`メソッドがあります。多くのサブクラスは子要素のリストではなく、一つの子要素のみをメンバ変数として保持してサポートします。例えば、`RenderPadding`は一つの子要素のみをサポートするので、結果、シンプルで実行に時間を要しないレイアウト処理メソッドとなっています。

(RenderObject.visitChildren)
(RenderShiftedBox._child)

- 可視化された`Render`ツリーと、論理的な`Widget`ツリー
Flutterでは、`Render`ツリーはデバイスに依存しない視覚座標系で動作します。つまり、現在の読み取り方向が右から左であっても、x座標の値が小さいほど常に左になります。。`Widget`ツリーは通常、論理座標で動作します。つまり、視覚的な解釈は読み取り方向によって異なります。論理座標から視覚座標への変換は、`Widget`ツリーと`Render`ツリーの間のハンドオフで行われます。`Render`ツリー内のレイアウトと描画の計算は、`Widget`から`Render`ツリーへのハンドオフよりも頻繁に行われ、座標変換の繰り返しを避けることができるため、このアプローチはより効率的です。

- `Text`は特別な`RenderObject`によって処理される
`RenderObject`の大多数は、`Text`の複雑さを知りません。代わりに、`Text`は特殊な`RenderObject`である、`RenderParagraph`によって処理されます。 テキスト対応の`RenderObject`をサブクラス化するのではなく、開発者はコンポジションを使用して`Text`をユーザーインターフェイスに組み込みます。 このパターンは、ツリーの走査中であっても、その親が同じレイアウト制約を提供する限り、RenderParagraphはそのテキストレイアウトの再計算を回避できることを意味します。

(RichText.createRenderObject -> RenderParagraph)

- `Observable`なオブジェクト
Flutterはモデル監視とリアクティブパラダイムの両方を使います。 明白に、リアクティブパラダイムが支配的ですが、Flutterはいくつかの葉のデータ構造に対して観測可能なモデルオブジェクトを使用します。 たとえば、Animationsは、値が変わるとオブザーバリストに通知します。 Flutterはこれらの観察可能なオブジェクトを`Widget`ツリーから`Render`ツリーに渡します。これはそれらを直接観察し、変更時にパイプラインの適切なステージのみを無効にします。 たとえば、_Animation_を変更すると、ビルドフェーズとペイントフェーズの両方ではなく、ペイントフェーズだけがトリガされることがあります。


これらを踏まえた上で、積極的なコンポジションで作られた木構造をまとめると、これらの最適化はパフォーマンスへの大きな影響を与えます。

# 無限スクロール
無限スクロールリストは知っての通り、ツールキットの課題となっています。Flutterは`Build`パターンに基づいてシンプルなインターフェイスで、無限スクロールリストをサポートします。`ListView`はスクロール中にユーザーに見えるようになると、必要に応じてコールバックを使用して`Widget`を作成します。

## 可視化されたレイアウト
Flutterのほとんどのものと同様、スクロール可能な`Widget`はコンポジションを用いて構築されています。スクロール可能な`Widget`の外側は`ViewPoint`で、これは「内側の方が大きい」ボックスです。つまり、その子は`ViewPort`の境界を超えて拡大でき、View内にスクロールできます。ただし、`RenderBox`の子を持つのではなく、`ViewPort`には、`sliver`と呼ばれる`RenderSliver`の子があります。これは、`ViewPort`対応のレイアウトプロトコルを持ちます。

`Sliver`レイアウトのプロトコルは、`Box`レイアウトのプロトコルの構造と一致しています。つまり、親は子に制約を渡し、代わりにジオメトリを受け取ります。ただし、制約とジオメトリデータは2つのプロトコル間で異なります。`Sliver{プロトコルでは、子供にはビューポートに関する情報（残りの可視スペースの量を含む）が与えられます。 返されるジオメトリデータによって、折りたたみ式のヘッダーや視差など、さまざまなスクロールリンク効果が有効になります。

`Sliver`が異なると、ビューポートで使用可能なスペースをさまざまな方法で埋めます。例えば、子要素の線形リストを作成する`Sliver`は、`Sliver`が子要素を使い果たすか空間を使い果たすまで順番に各子要素をレイアウトします。同様に、子要素の2次元グリッドを作成する`Sliver`は、表示されているグリッドの部分だけを塗りつぶします。それらはどのくらいのスペースが見えるかを知っているので、たとえそれらが無限の数の子供を生み出す可能性を持っていても、`Sliver`は有限数の子供を生み出すことができます。

`Sliver`はオーダーメイドのスクロール可能なレイアウトと効果を作成するために構成することができます。例えば、単一の`ViewPoint`は、折りたたみ可能なヘッダーとそれに続く線形リスト、それからグリッドを持つことができます。3つの`Sliver`はすべて、`Sliver`レイアウトプロトコルを介して協調して、ビューポートから実際に見える子だけを生成します。それらの子がヘッダ、リスト、またはグリッドのいずれに属しているかは関係ありません。

## オンデマンドで`Widget`を構築する
…