はじめに
連載第3~4回にかけてRxSwiftで処理を簡潔に記述できること、MVVMモデルを導入することにより機能を分離してクラスを管理できることなど、RxSwiftを利用した、基本的な画面の作成方法について主に紹介しました。今回はもう少し深くクラス分けについての考察を行い、もっと便利に利用できる方法などについて説明します。
MVVMモデルをもっとわかりやすく便利に利用する
RxSwiftはRxが本来持っている性質とともに、Observer/Observable関連クラスも充実しています。このことは、言い換えるとアプリのどの場面でも同じ仕様のクラスが同じように使えてしまうという意味です。スマートフォンアプリの画面の表示には、必ずと言っていいほどタッチなどの動作を含めた入力と画面への表示としての出力を伴います。今回はこの入出力に注目してRxSwift/MVVMモデルの導入を考えてみます。
RxSwiftでの入出力の扱いについて
連載第4回のサンプルのWikipedia検索アプリでは、検索ワード(searchWord: Variable<String>)と検索結果のリスト(items: Variable<[Result]>)はViewModel内で並列に定義されていました。2つの変数は一見したところでは、入力のための変数なのか、出力のための変数なのか区別はつきません。処理内容までソースコードを追って初めて、どういう目的の変数かわかります。
RxSwiftは、書き方も容易で導入がしやすいライブラリです。その反面、さまざまな処理で利用できるため、似たような変数や処理が並んでしまうとかえって扱いに困ることもあります。
その解決方法の1つとして、連載第4回で紹介した、Swiftのプロトコルの使い方を利用してRxSwiftの入出力をわかりやすくできる方法があります。
入出力変数をプロトコルで管理する
入出力の変数を区別するために、ViewModelのクラスを作成する際に変数をプロトコルで分けて定義するという方法があります。ViewからViewModelのプロパティを利用する際には、入力なのか出力なのかが分かるようにする方法です。
検索ワード(searchWord: Variable<String>)を入力、検索結果のリスト(items: Variable<[Result]>)を出力の変数としてプロトコルで定義する例は次のようになります。
protocol ViewModelInputs { var searchWord: Variable<String?> { get } } protocol ViewModelOutputs { var items: Observable<[Img]> { get } }
2つの変数を { get } で読み込み専用と定義しているのは、「プロトコルを実装するクラスの初期化処理でデータバインドを行い、クラスの外からの参照を読み込みのみで行う」といった意味です。ただしSwiftの仕様では、プロトコルの実装の際に変数の読み込みができていれば、値を書き込み、「var」を「let」で扱うことも許可されています。この辺りのSwiftの仕様はあまり厳密ではありません。
入出力のプロトコルを定義した後は、入出力のプロトコル自体にプロパティでアクセスできるプロトコルを次のように定義します。
protocol ViewModelType { var inputs: ViewModelInputs { get } var outputs: ViewModelOutputs { get } }
上記のプロトコルを実装したクラスでは、入力用の検索ワードにはinputs.searchWordプロパティで、出力用の検索結果のリストにはoutputs.itemsプロパティでアクセスすることができます。具体的な例は次の通りです。
class ViewModel: ViewModelInputs, ViewModelOutputs, ViewModelType { // MARK: - Properties var inputs: ViewModelInputs { return self } var outputs: ViewModelOutputs { return self } // Input Sources let searchWord = Variable<String?>(nil) // Output Sources let items: Observable<[Result]> # 後略
クラスの外からは、inputs/outputsを変数の前につけることで入出力を区別して変数にアクセスできます。クラスの内部では、inputs/outputsをつけずに変数を利用することができます。このようにプロトコルを利用することで、入出力の変数を区別して扱うことができます。