便利なObserver/Observable関連クラス
RxSwiftのオブザーバーパターンで利用できるクラスは、連載第2回で紹介したもの以外にもあります。これらのクラスをうまく使うことで、処理を分離して管理することが可能です。つまり、オペレーターで長々と処理を続けて記述することを回避し、ソースコードの保守性を上げることができます。本節では、よく使われるObserver/Observable関連クラス3つとそれらの基本的な使い方について説明します。
UI関連処理で利用されるDriverクラス
RxSwiftのObserverパターンでは、onNext/onError/onCompletedの各イベントが通知されます。しかし、前節のサンプルで考えると、UISearchBarやUITableViewなどのUIにonErrorのイベントが通知されてもUIを止めるわけにもいかず処理に困ります。
そこでUI関連の処理では、UI関連のObservable的に定義されたDriverクラスを利用します。Driverクラスの特徴とその利点は次の通りです。
特徴 | 利点 |
---|---|
onErrorを通知しない | 途中でUIが動かなくなるなどの不具合を回避できる |
イベントの通知をメインスレッドで行う | 結果をすぐUIに反映できる |
基本的に通常のObservableオブジェクトをasDriver()メソッドでDriverオブジェクトに変更した後で、subscribe(_:)メソッドの代わりにdrive(_:)メソッドを利用します。drive(_:)メソッドの書式は次の通りです。
#Driverオブジェクト#.drive( onNext: { 値 in 処理 }, onCompleted: { 処理 } )
driver(_:)メソッドは、onErrorイベントを受信しないsubscribe(_:)メソッドと考えて問題ありません。連載第2回のUIのサンプルをDriverクラスで書き換えると次のようになります。
sampleButton.rx.tap.asDriver().drive( onNext: { print( " tap ! " ) } ).disposed(by: disposeBag) sampleSwitch.rx.isOn.asDriver().drive( onNext: { bool in print( bool ? " ON " : " OFF " ) } ).disposed(by: disposeBag)
rs拡張プロパティで得られるオブジェクトはObservableオブジェクトなので、asDriver()を使ってDriverオブジェクトに変換してdrive(_:)メソッドで処理を記述します。サンプルを実行すると、subscribe(_:)メソッドを使う時と変わらない結果を確認できます。
ObservableとObserverの両方で機能するSubject
連載第2回では、Observableクラスを利用してObservableオブジェクトを生成する方法を説明しました。その他にも、Rxの共通する概念であるSubjectを利用する方法もあります。Subjectは、ObservableとObserverの両方で機能するという性質を持つ概念です。Subjectの性質を整理すると、次のことが言えます。
機能 | 概要 | 別の言い方 |
---|---|---|
Observable | onNext/onError/onCompletedのイベントを発生 | 任意のタイミングで各イベントを発生できる |
Observer | subscribe時の処理を定義 | イベント発生時の処理を自由に定義できる |
上記を考慮すると、onNextイベントが発生した際に実行する処理を先に定義しておき、任意のタイミングでonNextイベントを発生させてその処理を実行するといった動作も可能です。ここではRxSwiftでSubjectの機能が実装されたクラスを2つ紹介します。
1.PublishSubjectクラス
PublishSubjectクラスは、Subjectの機能が実装されたもっともシンプルなクラスです。型を指定して初期化した後に、subscribe(_:)メソッドでイベント発生時の処理をクロージャで定義します。PublishSubjectクラスの具体的な使い方は次の通りです。
// 初期化 let subject = PublishSubject<String>() // subscribeでイベントが発生した際の処理を先に定義 subject.subscribe({ print($0) }).disposed(by: disposeBag) // イベントを発生 subject.on(.next("a")) // 「next(a)」を出力 subject.on(.next("b")) // 「next(b)」を出力 subject.on(.next("c")) // 「next(c)」を出力 subject.onCompleted() // 「completed」を出力
on(_:)メソッドでイベントを発生させると、クロージャで定義した通りにそのイベント内容を出力することがわかります。イベント発生時の処理を定義するsubscribe(_:)メソッドは、連載第2回で説明したイベントを購読するsubscribe(_:)メソッドとは意味が異なるので注意してください。メソッドの名前は同じですが、引数と利用する目的が異なります。
2.Variableクラス
Variableクラスはvalueプロパティで値を指定できるSubjectです。Variableクラス自体にはあまり機能はなく、主に他のオブジェクトと結合して処理間を仲介するような使われ方をします。Variableクラスでは、valueプロパティで指定する値が変化した時にonNextイベントが発生します。
Variableクラス自体は、RxSwiftの仕様から削除される予定です。ですが、Variableクラスを採用したRx関連ライブラリが多数存在することと、初学者がRxSwiftの動作を確認するのにわかりやすいことを踏まえて、本連載ではVariableクラスを使って説明を進めます。Variableクラスの具体的な使い方は次の通りです。
// 初期値を指定して初期化 let variable = Variable<String>("text") // subscribeでイベントが発生した際の処理を先に定義 variable.asObservable().subscribe(onNext: { print($0)} ).disposed(by: disposeBag) // 「text」を出力 // 値を置き換えてonNextイベントを発生 variable.value = "a" // 「a」を出力 variable.value = "b" // 「b」を出力
Variableクラスは型と値を指定して初期化します。この時点で値が変化すれば、onNextイベントを発生します。また、イベント発生時の処理内容を定義する際には、asObservable()メソッドでObservableオブジェクトとして連載第2回で説明したイベントを購読するsubscribe(_:)メソッドでイベント発生時の処理内容を定義します。先のPublishSubjectクラスとはイベント発生時の処理の定義が異なるので気をつけてください。valueプロパティで値を指定するたびに、イベント発生時の処理が実行されます。
このような同じObservable/Observerでも仕様と動作が異なるクラスがなぜ存在するのか、次項で具体的なサンプルを交えて説明します。
Subject関連クラスを使って処理を分ける
前項で説明したPublishSubkect/Variableクラスを使って次のサンプルを作成してみます。よくある登録画面などで入力文字数による送信ボタンの制限と送信処理を行うものです。
最初に、入力テキスト用にVariableオブジェクト、送信処理定義用にPublishSubjectオブジェクトを定義します。その他、UIに関してはそれぞれStoryborad上で定義しておきます。
class ObSampleViewController: UIViewController { let inputText = Variable<String>("") // 入力テキスト用 let submitTrigger = PublishSubject<Void>() // 送信処理定義用 let disposeBag = DisposeBag() // メモリ解放用 @IBOutlet weak var textView: UITextView! // 入力欄 @IBOutlet weak var restLabel: UILabel! // 文字数表示ラベル @IBOutlet weak var submitButton: UIButton! // 送信ボタン # 後略
その後に、ビューコントローラーのviewDidLoad()メソッド内に処理内容を記述します。
// 入力欄の内容をinputTextにbind self.textView.rx.text.orEmpty.bind(to: self.inputText).disposed(by: disposeBag) // -------(1) // inputTextを監視 self.inputText.asObservable().subscribe(onNext: { [weak self] str in // -------(2) self?.submitButton.isEnabled = str.count > 10 self?.restLabel.text = "残り\(200-str.count)文字" }).disposed(by: disposeBag) // 送信ボタン押下時に実行される処理を定義 submitTrigger // -------(3) .subscribe(onNext: { print("送信処理を実行します") }).disposed(by: disposeBag) // 送信ボタン押下時にsubmitTriggerの処理内容を実行 submitButton.rx.tap.asDriver() // -------(4) .drive(self.submitTrigger) .disposed(by: disposeBag)
先に、入力欄の入力内容のテキストをVariableオブジェクトであるinputTextに結合します(1)。そのinputTextに対して、onNextイベント発生時の処理を定義します(2)。入力欄の内容に変更があるたびに、onNextイベントが発生します。入力内容が10文字以上であれば送信ボタンを押下可能にする/残り文字数の表示という2つの処理を行います。
PublishSubjectオブジェクトであるsubmitTriggerには、送信処理の内容を定義します(3)。サンプルでは「送信処理を実行します」をコンソールに出力するだけの処理を定義しています。この処理を送信ボタン押下時に実行されるようにします。
送信ボタン押下時の動作は、送信ボタンのタップイベントをDriverオブジェクトに変換して行います。変換したDriverオブジェクトのdriveメソッドを利用して、submitTriggerにonNextイベントが発生したことを伝えます(4)。このようにして事前に定義した送信処理を送信ボタンの押下で呼び出すことができます。サンプルを実行すると、本節の冒頭で示した登録画面の動きが確認できます。
Variableクラス/PublishSubjectクラスを利用することで、処理内容を事前に定義でき、UIと分離して記述できることがわかります。動作が行われる部分と処理の内容を分離できることは、ソースコードの見通しがよくなり管理も容易にできることと同じ意味です。また、処理を分離した部分は別のクラスに移すことも可能ですので、連載第1回で説明したMVVMモデルの設計思想にもつながります。
まとめ
今回は、RxSwiftのオペレーターを利用して複数の処理をつなぐ/Subjectを利用して処理を分ける方法について、簡単な例を挙げて説明しました。このことは統一した設計思想の採用やソースコードの管理を容易にする目的に沿ったものです。RxSwiftをアプリ開発に採用するメリットを、サンプルを通して多少なりとも実感できたと思います。次回以降は、Rx関連ライブラリやMVVMモデルの具体的な導入などについて説明を進めます。