Shoeisha Technology Media

CodeZine(コードジン)

特集ページ一覧

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

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

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

 連載第3回では、RxSwiftのObserver関連オペレーターやクラスについて基本的な説明を行います。前回まではObserverパターンから得られるデータストリームの扱いに関して説明しました。今回は、データストリームがどのようにアプリ内部の処理と関係するかに関して、もう少し踏み込んで解説します。本連載では、これからRxSwiftを導入する方を対象読者としている関係上、RxSwift導入の初歩的な手順に関して主な説明を行います。そのため、便宜上クラス/メソッドの使い方が必ずしもRxSwiftの詳細な仕様通りでないこともあります。この点をご了承ください。

目次

はじめに

 アプリ開発を進めるにあたっては、Observerパターンの監視対象から値を取得した後の処理の方が重要です。連載第2回のサンプルのように、データストリームから得た値を単発の処理のみで利用するケースは実際のアプリ開発ではまれです。具体的には、「データストリームを加工して次の処理に渡す」「その際に複数の処理を組み合わせる」「長くなりがちな処理を分離して管理しやすくする」などの方法が、開発の現場では必要です。それらの基本的な処理について順を追って説明します。

データストリームを加工するオペレーター

 以前、データストリームから得られる値は時間軸をベースとした配列で扱えることを説明しました。得られた値が配列として扱える以上、SwiftのArrayクラスの要素を操作するメソッドなども利用できます。本節では、これらの配列の要素を操作するメソッドを利用してデータストリームを加工したり処理をつなげたりする基本的な方法について説明します。

オペレーターを利用する

 RxSwiftでは、配列の要素を操作するメソッドをオペレーターと呼びます。オペレーターは、Arrayクラスのメソッドと基本的に同じですが、ArrayクラスではcompactMapに置き換えられたflatMapが利用できるなど若干の違いがあります。RxSwiftの場合は、オペレーターはデータストリームを制御するという意味で使われます。RxSwiftのドキュメントに沿って主なオペレーターを表にまとめます。

表:主なオペレーター
名前 概要 
map  データストリームを別のデータストリームに変換
filter  条件に合わないデータストリームを排除
merge  複数のデータストリームを統合
flatMap  前のデータストリームを維持して次のデータストリームを処理
flatMapLates  前のデータストリームをキャンセルして次のデータストリームを処理
zip  複数のデータストリームが存在する場合に全ての処理が終わるまで待って統合

 ドキュメントでは、「データストリーム」という表現ですが、実際にはデータストリームから得られる値をどう扱うかといったイメージの方が強いです。複数のオペレーターをつないで連続して処理を行うメソッドチェーンも利用できます。filterとmapを使った、オペレーターの簡単な利用例は次の通りです。

[リスト1]TextFieldViewController.swift
class TextFieldViewController: UIViewController {

    @IBOutlet weak var textField: UITextField!
    @IBOutlet weak var textLabel: UILabel!
    let disposeBag = DisposeBag()
    
    override func viewDidLoad() {
        super.viewDidLoad()

        self.textField.rx.text.orEmpty
            .filter { $0.count >= 3 }                                      // 制限
            .map { "入力した文字数は\($0.count)です"}  // 加工
            .bind(to: self.textLabel.rx.text)                          // 結合
            .disposed(by: disposeBag)
    }
}

 最初に、入力欄が空の場合は処理を行わないorEmptyプロパティを利用して、値が空の場合は以降の処理を進めないようにします。その後にfilterで文字数が3文字以上の要素だけ抽出します。つまり、文字数が3文字未満の場合は以降の処理は行いません。ここまでの条件を通過した後に、mapで値を加工してラベルに結合します。

filter/mapオペレーターの利用例
filter/mapオペレーターの利用例

 サンプルを実行すると、上記のように入力文字数が3文字以上の場合に、文字数を表示する動きが確認できます。

オペレーターで処理をつなぐ

 データストリームはオペレーターで加工するだけでなく、別のクラスやメソッドに渡して処理を行うことも可能です。処理の結果を次のオペレーターで別の処理に渡して、さらに次の処理を行うなどすれば、短いコードで複数の処理を連続して行うことも可能です。この一連の処理について、サンプルを作成しながら説明します。作成するサンプルは下図の通りです。検索欄に入力された検索キーワードを元にWikipediaで検索を行い、検索結果をテーブルに表示するアプリです。

複数の処理をつなぐサンプル
複数の処理をつなぐサンプル

 WikipediaのAPIの詳細に関しては公式ページで確認できます。サンプルでは検索結果をJSONで出力する形式でAPIを利用します。

 Storyboard上でUISearchBarとUITableViewを配置してIBOutletでつないだ後に次の処理を記述します。サンプルでは、各オペレーターの役割を見やすくするために間に改行を入れています。

[リスト2]TextFieldViewController.swift
class WikipediaSearchViewController: UIViewController {

    @IBOutlet weak var searchBar: UISearchBar! 
    @IBOutlet weak var tableView: UITableView!
    let disposeBag = DisposeBag()

    override func viewDidLoad() {
        super.viewDidLoad()

        self.searchBar.rx.text.orEmpty
            .filter { $0.count >= 3 }    // -------(1)
            
            .map {
                let urlStr = "https://ja.wikipedia.org/w/api.php?format=json
                                        &action=query&list=search&srsearch=\($0)"
                return URL(string:urlStr.addingPercentEncoding(withAllowedCharacters: 
                   NSCharacterSet.urlQueryAllowed)!)!
            }       // -------(2)
            
            .flatMapLatest { URLSession.shared.rx.json(url: $0) }    // -------(3)
            
            .map { self.parseJson($0) }    // -------(4)
            
            .bind(to: tableView.rx.items(cellIdentifier: "Cell")) { index, result, cell in
                cell.textLabel?.text = result.title
                cell.detailTextLabel?.text = "https://ja.wikipedia.org/w/index.php?curid=\(result.pageid)"
            }     // -------(5)
            .disposed(by: disposeBag)
    }
# 略

 前回のサンプルと同様に、文字数が3文字以上の条件をfilterオペレーターで指定します(1)。検索欄のテキストからそのままデータストリームを流す処理なので、この時点でインクリメンタルサーチも実装できます。

 次にmapオペレーターで、渡されたデータストリームの値からWikipediaのAPIを呼び出すURLを生成します(2)。クエリの引数として全角文字を扱うオプションでURLオブジェクトを生成し、次のオペレーターに渡します。

 渡されたURLからURLSessionオブジェクトのRx拡張jsonメソッドでAPIの戻り値のJSONを取得します(3)。検索欄の入力値が変わるたびにAPIを新しく呼び出すという意味でflatMapLatestオペレーターを使用しています。

 (3)で得られたJSONをパースします(4)。JSONをパースする処理は、同じビューコントローラー内のparseJsonメソッドです。parseJsonメソッドの内容は次の通りです。

[リスト3]TextFieldViewController.swift
func parseJson(_ json: Any) -> [Result] {
    guard let items = json as? [String: Any] else { return [] }
    var results = [Result]()
    // JSONの階層を追って検索結果を配列で返す
    if let queryItems = items["query"] as? [String:Any] {
        if let searchItems  = queryItems["search"] as? [[String: Any]] { // -------(4-1)
            searchItems.forEach{
                guard let title = $0["title"] as? String,
                        let pageid = $0["pageid"] as? Int else { return }
                results.append(Result(title: title, pageid: pageid))    // -------(4-2)
            }
        }
    }
    return results  // -------(4-3)
}
# 中略

// 検索結果を格納する構造体
struct Result {
    let title: String
    let pageid: Int
}

 Wikipedia API の JSON出力フォーマットは次の通りです。

Wikipedia API の JSON出力フォーマット
Wikipedia API の JSON出力フォーマット

 APIの戻り値はJSONオブジェクトで取得済みなので、JSONのキーである「query」から「search」までの階層を追って検索結果を取得します(4-1)。検索結果を格納するためにResult構造体を定義しておきます。構造体の内容は、JSONのキーと値の型のみです。検索結果を個々にResult構造体に格納し(4-2)、配列の形式で次のオペレーターに渡せるように返却します(4-3)。

 (4)の処理でJSONをパースした配列をテーブルの内容に結合し、セルに表示します(5)。この時にクロージャにはインデックス番号、結合する個々のデータ、テーブルセルのオブジェクトが渡されます。結合する個々のデータはJSONの解析結果のResult構造体なので、検索のタイトルをセルのtextLabelに、ページIDはWikipediaのリンクに加工してセルのdetailTextLabelに表示します。

  (1)から(5)までの処理の流れをまとめると次の図のようになります。

(1)から(5)までの処理の流れ
(1)から(5)までの処理の流れ

 サンプルを実行すると、次のように入力したキーワードに関連するWikipediaの項目が確認できます。

Wikipediaの検索結果の表示
Wikipediaの検索結果の表示

 UISearchBarから流れてきたデータストリームがオペレーターを通して複数の処理を経て加工され、最終的にUITableViewのセルに表示されることが実感できます。


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

著者プロフィール

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

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

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

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

バックナンバー

連載:RxSwiftで一歩進んだiOSアプリ開発
All contents copyright © 2005-2018 Shoeisha Co., Ltd. All rights reserved. ver.1.5