はじめに
ふとViewControllerの役割について気になったのでドキュメントを読んで、まとめてみる。 View Controller Programming Guide for iOS
ViewControllerの役割
ViewControllerの役割として以下を持ちます。
- Viewの管理
- イベントの処理
- あるViewControllerから別のViewControllerへの遷移
- アプリの他の部分との調整を行う
また、ViewControllerは2つの種類があります。ほとんどのアプリでは、両方のViewControllerが混在しています。
- ContentViewController
- アプリ内の特定のコンテンツを管理するために作成されるViewControllerのこと
UITableViewController
やUICollectionViewController
が該当するような気がする
- ContainerViewController
- 他のViewController(child view controllers)をまとめて管理し、アプリ内のナビゲーションやコンテンツの表示方法を制御するViewControllerのこと
UINavigationController
やUITabBarController
が該当する
Viewの管理について
ViewControllerの最も重要な役割は、Viewの階層を管理することです。
すべてのViewControllerには、ViewControllerのすべてのコンテンツを含む単一のRootViewがあります。 そのRootViewに、コンテンツを表示するために必要なViewを追加します。
図1-1は、ViewControllerとそのView間の組み込み関係を示しています。 ViewControllerには常にRootViewへの参照があり、各Viewにはsubviewsへの強参照があります。
ContentViewControllerは、そのすべてのViewを独自に管理します。
一方で、ContainerViewControllerは、自身のViewに加えて、1つ以上の子ViewControllerのRootViewを管理します。 コンテナは、子のコンテンツを管理しません。 コンテナはRootViewのみを管理し、コンテナのデザインに従ってサイズと配置を決定します。 図1-2は、SplitViewControllerとその子の関係を示しています。 SplitViewControllerは、子Viewの全体的なサイズと位置を管理しますが、子ViewControllerは、それらのViewの実際のコンテンツを管理します。
Data Marshaling(データマーシャリング)
アプリを開発する際、データはしばしば異なる形式で存在しています。 これらの形式の違いを解決するプロセスをデータマーシャリングといいます。 例えば、DBやAPIから取得したデータをアプリ内で使える形に変換する必要があります。 逆に、ユーザが入力したデータをサーバに送信する際も適切な形に変換する必要があります。
図1-3は、ViewControllerがデータ(Custom Data Object)とデータの表示に使用されるViewを参照している様子を表しています。 これらの間でデータを移動させるのは、開発者の責任です。
また、ViewControllerとDataObject(APIのレスポンスデータやCoreDataのデータ)の責務は常に分けるべきです。 データのバリデーションや保存処理は、DataObject内で行うべきです。 ViewControllerはViewからの入力を受け取り、その入力をDataObjectが要求する形に変換するかもしれませんが、ViewControllerが実際のデータを管理する役割は最小限にすべきです。
User Interactions(ユーザーインタラクション)
ViewControllerはレスポンダオブジェクトであり、Responder Chainに流れてくるイベントを処理することができます。 しかしながら、ViewControllerが直接タッチイベントを処理することはほとんどありません。
その代わりに、通常はViewが自身のタッチイベントを処理し、その結果を関連付けられたデリゲートメソッドやターゲットオブジェクト(通常はViewController)のメソッドに報告します。
このため、ViewController内のほとんどのイベントは、デリゲートメソッドまたはアクションメソッドを使用して処理されます。
リソース管理
UIViewController は基本的なビュー管理を自動で行い、ビューが不要になった場合にリソースを解放してくれます。 しかし、自分で作成したオブジェクトは、自分で管理しなければなりません。
アプリが動作している環境で空きメモリが不足した場合、システムはdidReceiveMemoryWarning
というメソッドを呼び出して、不要なメモリを解放するように要求します。
一時的なデータや簡単に再作成できる情報はここで削除して、メモリを節約できます。
メモリを多く使用しすぎて解放できない状態が続くと、アプリがクラッシュする原因になります。 システムがメモリを回復するためにアプリを強制終了することがあるため、メモリの解放はパフォーマンスと安定性において非常に重要です。
Adaptivity(適応性)
ViewControllerは、アプリの画面表示を管理し、さまざまデバイス(iPhoneやiPadなど)の画面サイズの違いに応じてViewを調整する役割を持っています。
Viewを調整するために、iOSでは「サイズクラス」という概念を使います。 例えば、画面が広いとき(iPadなど)はコンテンツを横に広く配置し、画面が狭いとき(iPhoneなど)は縦に重ねて表示する、といった調整が行えます。
また、画面が回転したり、細かいサイズ変更があった場合でも、Auto Layoutを活用すれば、自動でビューのレイアウトを調整してくれます。 この仕組みによって、複数のデバイスや画面サイズに対応するシンプルで柔軟なUI設計が可能になります。
まとめていて思ったこと
ViewControllerが「FatViewController」になってしまう話をよく耳にしますが、 これはViewControllerに余計な責務を持たせていることが原因だと考えます。 自分の経験でも、DataObjectとの責務分担がうまくいっていないコードがありました。
例えば、画面遷移のロジックをRouterとして分離するなど責務を分割し、できるだけFatViewControllerにならないようにする工夫が必要だと思いました。