MVVMモデルをもっとわかりやすく便利に利用する(2)
入出力を重視したRxSwift派生ライブラリ
RxSwiftには、RxSwift Communityでさまざまな派生ライブラリが開発されています。その中の1つに、入出力に重点を置いたライブラリ「Action」があります。Actionライブラリは、Swiftで行う処理を抽象化し、入力/出力/エラー/処理中の各値をプロパティから得るインターフェースを持ちます。具体的にはActionクラスで処理を定義し、次の4つのプロパティで処理に関する値を指定、参照します。
名前 | 概要 |
---|---|
inputs |
Actionクラスで定義した入力変数
|
elements | Actionクラスで定義した出力変数 |
errors | エラー発生時にエラー情報をAction.Errorで返す |
executing | 処理中かどうかをtrue/falseで返す |
Actionクラスで処理を定義する際は、第一引数をinputsプロパティで、第二引数をonNextで渡す変数として定義します。
let アクション名: Action<入力用の変数の型, 出力用の型> = Action { (入力用の変数) in // 処理 return 出力用の変数 }
Actionクラスを初期化する際に、先に入出力を行う変数の型を定義します。この時に定義する入出力の変数がそれぞれinputs/elementsプロパティの値となります。
inputsプロパティに入力変数の値が入った際に、Actionクラスで定義した処理が実行されます。Actionクラスの処理内容はクロージャで定義します。渡されるのは入力用の変数です。クロージャ内の処理を行った後に、出力用の変数を返すように定義します。動作を定義する際には入出力の変数は型だけで定義していますが、利用する際はObservable変数として値の受け渡しを行います。この点に気をつけてください。
Actionを利用する具体的な例として、IDとパスワードでログインするフォームの処理を、Actionクラスで実装するサンプルを次に示します。
class LoginSampleViewController: UIViewController { @IBOutlet weak var idTextField: UITextField! // ID入力欄 @IBOutlet weak var passwordTextField: UITextField! // パスワード入力欄 @IBOutlet weak var loginButton: UIButton! // ログインボタン private let disposeBag = DisposeBag() override func viewDidLoad() { super.viewDidLoad() // ログインの処理をActionで定義 // IDとパスワードを入力してログイン処理後に結果をBoolで返す let loginAction: Action<(String,String), Bool> = Action { // ----------------(1) (username, password) in // ログインが成功したものとしてtrueを返す return Observable.just(true) } // IDとパスワードをObservable化 let idAndPassword = Observable.combineLatest(idTextField.rx.text.orEmpty, passwordTextField.rx.text.orEmpty) // ----------------(2) // ログインボタン押下時にloginActionのinputsにIDとパスワードをbind loginButton.rx.tap.asObservable() .withLatestFrom(idAndPassword) .bind(to: loginAction.inputs) .disposed(by: self.disposeBag) // ----------------(3) // loginActionの処理結果を受けてログイン後の処理を行う loginAction.elements.subscribe( onNext:{ bool in // ----------------(4) if bool { print("login ok!") } else { print("login fail") } }).disposed(by: self.disposeBag) } # 略
最初にActionクラスの処理を定義します(1)。入力用の変数はIDとパスワードのString型の変数、戻り値はBool型です。入力だけでなく、処理の後に返す出力用の変数の型も処理の前に指定します。サンプルでは便宜上、常にtrueを返すようにしています。ObservableクラスのcombineLatestメソッドで、IDとパスワードを1つのデータストリームに結合します(2)。
ログインボタンが押された場合に、(2)のデータストリームがActionクラスのinputsプロバティにデータバインドし、値が渡るようにします(3)。Actionクラスのinputsプロバティに入力用の値が渡った時に、(1)で定義したActionクラスの処理が行われます。Actionクラスの処理が終わり次第、elementsプロパティで処理の結果を得られます(4)。
Actionクラスのelementsプロパティの値を監視することで、Actionクラスの処理の戻り値の取得とその後の処理を行うことができます。
サンプルの流れのイメージは次の図の通りです。
Actionクラスを利用すると、このように処理の過程を監視するのではなく、処理の入力と出力のみを監視することで機能を実装することができます。
今回利用するライブラリ
今回のサンプルで利用するライブラリは次の通りです。
ライブラリ名 | 概要 |
---|---|
Action | RxSwift拡張 |
RxOptional | RxSwiftオペレーター拡張 |
Kanna | HTML解析 |
AlamofireImage | 非同期の画像読み込み |
各ライブラリはCocoaPodsでインストールしてください。
target 'RxSample5' do use_frameworks! # Pods for RxSample5 pod 'RxSwift' pod 'RxCocoa' pod 'Action' pod 'RxOptional' pod 'Kanna' pod 'AlamofireImage' end
Action以外のライブラリの機能に関してはサンプルとともに説明します。
入出力を重視したクラス構成を考える
前節で紹介した各ライブラリを利用して、Yahoo!画像検索を利用した画像検索アプリを作成してみます。
作成するアプリの概要
画面の入力欄に入力したキーワードに基づいてYahoo!画像検索を行います。検索結果を解析して、コレクションビューに検索結果の画像を一覧で表示します。
検索キーワードが3文字以上で検索可能に/検索中はローディングを表示/エラーが発生した場合はエラー内容をアラートで表示します。このイメージは次の図の通りです。
Yahoo!画像検索は、APIは提供されていないので、画面のHTMLを解析して画像のURLのみを抽出します。
作成するアプリの各クラス
作成するサンプルの各クラスは、MVVMモデルに準じて次のように作成します。
クラス/構造体名 | 概要 |
---|---|
Img | 検索結果を格納 |
ViewController | 入力欄、検索ボタン、コレクションビューを表示 |
ImageItemCell | コレクションビューのセル |
SearchViewModel | 入出力変数の管理/Actionによる処理の制御 |
サンプルでは、Actionライブラリを利用したView-ViewModel間の入出力の変数の管理を目的としています。MVVMモデル内のModelの役割については、簡略化するためにサンプルでは省いています。前回に引き続き、View-ViewModel間の変数のやり取りにはデータバインドを用い、View以外ではUIKitフレームワークをimportしないようにします。
まとめ
今回はMVVMモデルを利用する上で、入力と出力の変数を区別して変数を定義することを中心に説明しました。これまでは、RxSwiftの使い方に主眼を置いて来ましたが、今回はRxSwiftをどう使うか、という例をあげました。次回は今回のサンプルの続きを作成しながら、入出力の変数を区別して、入力/出力/処理中/エラー発生時の具体的な処理方法について説明します。