RxSwiftを導入するメリット
RxSwiftを導入するとどういった点が便利なのかを、コードの書き方を紹介しながら説明します。RxSwiftを使った具体的なコードの書き方は連載第2回以降で扱うとし、ここでは簡単な紹介にとどめます。
UIの操作時の処理を簡潔に記述する
スマートフォンアプリでは、時間軸上で起こるイベントはUI上で起こる動作の検出と考えて差し支えありません。RxSwiftでUIの操作に応じた処理を宣言的に記述する例を2つあげます。RxSwiftの文法は第2回以降で説明するので、ここではRxSwift導入前/導入後のソースコードの比較のみを行います。
1)IBActionを利用する例
ボタンを押した際の処理と通常のSwiftとRxSwiftの書き方で比較すると次の通りになります。
import UIKit
class UIButtonViewController: UIViewController {
@IBAction func onPush(_ sender: Any) {
print("ボタンを押しました!")
}
#後略
class UIButtonRxViewController: UIViewController {
@IBOutlet weak var button: UIButton!
let disposeBag = DisposeBag()
override func viewDidLoad() {
super.viewDidLoad()
button.rx.tap.subscribe(onNext: { _ in
print("ボタンを押しました!")
}).disposed(by: disposeBag)
}
#後略
両方の処理を図にして比べると下図になります。
通常のSwiftの書き方では、ソースコード内の@IBActionで定義した処理はそのまま画面上のボタンと結合します。これは画面上のUIと処理が1対1で強く結合され、例えば画面上のUIを少し変更する場合でも、@IBAction の処理を改めて定義し直すことと同じ意味です。UIと処理は分離できません。
これに対してRxSwiftでは、画面上のUIと結合するのはソースコードのプロパティのみです。処理の部分は、UIには直接影響しないように記述できます。画面上のUIを変更する場合でも、ソースコードへの影響は少なくて済みます。UIと処理を分離して管理することが可能です。
2)delegateを利用する例
次によく利用される、delegateを経由した、入力欄に関する動作の例をあげます。
class UITextFieldViewController: UIViewController {
@IBOutlet weak var textField1: UITextField!
@IBOutlet weak var textField2: UITextField!
#中略
extension UITextFieldViewController : UITextFieldDelegate { // UITextFieldDelegate内で入力欄1と2の処理を定義
func textFieldDidBeginEditing(_ textField: UITextField) {
switch textField {
case textField1:
print("入力欄1で入力を開始しました")
case textField2:
print("入力欄2で入力を開始しました")
default:
break
}
}
}
class UITextFieldRxViewController: UIViewController {
@IBOutlet weak var textField1: UITextField!
@IBOutlet weak var textField2: UITextField!
let disposeBag = DisposeBag()
override func viewDidLoad() {
super.viewDidLoad()
textField1.rx.controlEvent([UIControlEvents.editingDidBegin]) // 入力欄1に対する処理を定義
.asDriver().drive(onNext: { _ in
print("入力欄1で入力を開始しました!")
}).disposed(by: disposeBag)
textField2.rx.controlEvent([UIControlEvents.editingDidEnd]) // 入力欄2に対する処理を定義
.asDriver().drive(onNext: { _ in
print("入力欄2で入力を終了しました!")
}).disposed(by: disposeBag)
}
#後略
両方の処理を図にして比べると次のようになります。
通常のSwiftの書き方では、UIに対応するdelegateのメソッドを呼び出すことで処理を行います。この場合は、UIViewControllerでdelegateを実装するため、処理が本来のビューコントローラーの部分と離れがちです。また複数の同じUIが存在する場合でも、呼び出されるdelegateのメソッドは1つなので、delegateのメソッド内でどのUIに対する処理かを分岐して記述する必要があります。
RxSwiftを利用すると、各UIのすぐ後ろにイベント処理を記述できます。UI単位で処理を記述できるため、delegateを利用する場合と違って処理の中で各UI別に処理を分岐する作業も必要ありません。
上記の例からわかる通り、RxSwiftを利用することでより簡潔に、よりインタラクティブに処理を記述できます。
アプリ全体の構造を統一したフォーマットで管理する
前項のサンプルで確認した通り、RxSwiftを利用することで、イベント処理や非同期処理をより簡潔に記述できます。これをうまく利用してアプリ全体のプログラム構造を統一したフォーマットのクラスで整理することもできます。システム全体を整理する際には、Webアプリケーションでよく利用される、MVCモデルが採用されることが多いです。iOSアプリ開発においても、MVC的な構造を利用するケースはよく見られます。
MVCモデルはWebアプリケーションの開発に最適化された構造です。スマートフォンアプリでは、ViewとControllerがWebアプリケーションより密接な関係にあり、どうしてもControllerの肥大化が避けられません。
言い換えると、スマートフォンアプリの開発では、MVCモデル以上の構造が必要になります。MVCモデルに変わる構造の1つとして、MVVMモデルがあります。MVVMモデルでは、ViewとModelの間にViewModelという構造を挟むモデルです。iOSアプリ開発で適応されるMVVMモデルは、次のような構造をしています。
各処理の概要は次の通りです。
- スマートフォンの画面上でUIの操作やセンサーの値の変化など、イベントの発生を検知
- ViewからViewModelへイベントで発生した処理の対応依頼を行う
- ViewModelからModelへオブジェクトの操作などの処理の対応依頼を行う
- Modelが処理を行い、その結果をViewModelへ渡す
- ViewModelからViewへデータなどを渡す
- Viewで画面上への処理を反映
それぞれの役割を簡単にまとめると次の通りです。
| 名前 | 概要 |
|---|---|
| Model | オブジェクトに関する操作 |
| ViewModel | ModelとViewの橋渡し |
| View | UIで発生するイベントの管理/画面上の表示 |
View内部では、ソースコードであるビューコントローラーとStoryboardのビュー部分が存在します。この2つはMVVMモデルでは明確に区別されていませんが、画面上のイベントや表示を担うものだと考えてください。
MVVMの各部分をクラスで分けることで、アプリ全体をすっきりとさせて一部のソースコードが肥大化することを防ぐことができます。さらにRxSwiftを採用すると、すべての過程でイベントから非同期処理の流れを同じように記述することができ、非常に楽にソースコードを管理できます。大規模なアプリをチームで開発する際には、RxSwiftとMVVMモデルを採用すると、開発者側で特別に規則を作らなくてもアプリ開発のすべての過程を統一したルールで進められるメリットがあります。
まとめ
今回はRxSwiftを導入するメリットと手順について簡単に説明しました。今回あげたUI以外の処理でも、RxSwiftを利用することで処理を簡潔かつ統一したフォーマットで記述することができます。RxSwiftのGitHubのページにもさまざまなサンプルが用意されているので、ぜひとも確認してみてください。連載第2回からは、もう少し具体的にRxSwiftの使い方を説明します。
