CodeZine(コードジン)

特集ページ一覧

RxSwiftでのデータストリームの処理について理解しよう

RxSwiftで一歩進んだiOSアプリ開発 第3回

  • LINEで送る
  • このエントリーをはてなブックマークに追加
2018/10/03 14:00
目次

便利なObserver/Observable関連クラス

 RxSwiftのオブザーバーパターンで利用できるクラスは、連載第2回で紹介したもの以外にもあります。これらのクラスをうまく使うことで、処理を分離して管理することが可能です。つまり、オペレーターで長々と処理を続けて記述することを回避し、ソースコードの保守性を上げることができます。本節では、よく使われるObserver/Observable関連クラス3つとそれらの基本的な使い方について説明します。

UI関連処理で利用されるDriverクラス

 RxSwiftのObserverパターンでは、onNext/onError/onCompletedの各イベントが通知されます。しかし、前節のサンプルで考えると、UISearchBarやUITableViewなどのUIにonErrorのイベントが通知されてもUIを止めるわけにもいかず処理に困ります。

 そこでUI関連の処理では、UI関連のObservable的に定義されたDriverクラスを利用します。Driverクラスの特徴とその利点は次の通りです。

表:Driverクラスの主な特徴
特徴 利点
onErrorを通知しない 途中でUIが動かなくなるなどの不具合を回避できる
イベントの通知をメインスレッドで行う 結果をすぐUIに反映できる

 基本的に通常のObservableオブジェクトをasDriver()メソッドでDriverオブジェクトに変更した後で、subscribe(_:)メソッドの代わりにdrive(_:)メソッドを利用します。drive(_:)メソッドの書式は次の通りです。

[リスト4]drive(_:)メソッドの書式
#Driverオブジェクト#.drive(
        onNext: { 値 in 処理 },
        onCompleted: { 処理 }
)

 driver(_:)メソッドは、onErrorイベントを受信しないsubscribe(_:)メソッドと考えて問題ありません。連載第2回のUIのサンプルをDriverクラスで書き換えると次のようになります。

[リスト5]UISampleViewController.swift
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の性質を整理すると、次のことが言えます。

表:Subjectの性質
機能 概要 別の言い方
Observable onNext/onError/onCompletedのイベントを発生 任意のタイミングで各イベントを発生できる
Observer subscribe時の処理を定義 イベント発生時の処理を自由に定義できる

 上記を考慮すると、onNextイベントが発生した際に実行する処理を先に定義しておき、任意のタイミングでonNextイベントを発生させてその処理を実行するといった動作も可能です。ここではRxSwiftでSubjectの機能が実装されたクラスを2つ紹介します。

1.PublishSubjectクラス

 PublishSubjectクラスは、Subjectの機能が実装されたもっともシンプルなクラスです。型を指定して初期化した後に、subscribe(_:)メソッドでイベント発生時の処理をクロージャで定義します。PublishSubjectクラスの具体的な使い方は次の通りです。

[リスト6]ViewController.swift
// 初期化
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クラスの具体的な使い方は次の通りです。

[リスト7]ViewController.swift
// 初期値を指定して初期化
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クラスを使って次のサンプルを作成してみます。よくある登録画面などで入力文字数による送信ボタンの制限と送信処理を行うものです。

PublishSubkect/Variableクラスを利用した登録画面のサンプル
PublishSubkect/Variableクラスを利用した登録画面のサンプル

 最初に、入力テキスト用にVariableオブジェクト、送信処理定義用にPublishSubjectオブジェクトを定義します。その他、UIに関してはそれぞれStoryborad上で定義しておきます。

[リスト8]ObSampleViewController.swift
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()メソッド内に処理内容を記述します。

[リスト9]ObSampleViewController.swift
// 入力欄の内容を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モデルの具体的な導入などについて説明を進めます。



  • LINEで送る
  • このエントリーをはてなブックマークに追加

バックナンバー

連載:RxSwiftで一歩進んだiOSアプリ開発

著者プロフィール

  • WINGSプロジェクト 片渕 彼富(カタフチ カノトミ)

    <WINGSプロジェクトについて> 有限会社 WINGSプロジェクトが運営する、テクニカル執筆コミュニティ(代表 山田祥寛)。主にWeb開発分野の書籍/記事執筆、翻訳、講演等を幅広く手がける。2018年11月時点での登録メンバは55名で、現在も執筆メンバを募集中。興味のある方は、どしどし応募頂...

  • 山田 祥寛(ヤマダ ヨシヒロ)

    静岡県榛原町生まれ。一橋大学経済学部卒業後、NECにてシステム企画業務に携わるが、2003年4月に念願かなってフリーライターに転身。Microsoft MVP for ASP/ASP.NET。執筆コミュニティ「WINGSプロジェクト」代表。 主な著書に「入門シリーズ(サーバサイドAjax/XM...

あなたにオススメ

All contents copyright © 2005-2021 Shoeisha Co., Ltd. All rights reserved. ver.1.5