RxSwiftとは?導入方法と使い方まとめ!ストリームを理解する

この記事からわかること

  • RxSwiftとは?
  • ReactiveX特徴メリット
  • 初学者がRxSwiftを理解するために重要なポイント
  • ストリームObservableなどの意味とは?
  • Operator種類
  • mapfilterの使い方

index

[open]

\ アプリをリリースしました /

みんなの誕生日

友達や家族の誕生日をメモ!通知も届く-みんなの誕生日-

posted withアプリーチ

この記事はRxSwiftのことを何も知らない状態から理解するための備忘録として作成します。至らぬ点や間違っている点がございましたら教えていただけると嬉しいです。また参考にさせていただいた記事は全て一番最後に記載しております。

重要そうな単語など

まずはいろいろな情報を見た結果、重要そうな単語などを整理しておきます。

なんとなく単語を見ていると雰囲気は掴めそうですがなかなか難しそうです。

RxSwiftとは?

そもそもRxSwiftとはMicrosoftが2011年にリリースしたReactiveX(Reactive Extensions)と呼ばれるライブラリのSwift版です。ReactiveXはもともと.NET Framework用に開発されたライブラリで、その後SwiftだけでなくC++やJava、JavaScriptなど数多くのプラットフォームにも提供されるようになりました。

ReactiveXの概要

ReactiveXの公式サイトによる説明は以下のようになっています。

”ReactiveXは、監視可能なシーケンスを使用して、非同期およびイベントベースのプログラムを作成するためのライブラリです。
オブザーバーパターンを拡張してデータやイベントのシーケンスをサポートし、低レベルのスレッド化、同期化、スレッド セーフ、並行データ構造、および非I/O をブロックします。”

引用:公式リファレンス:ReactiveX

要約すると非同期処理やイベント処理、時間経過に関数処理を1つのシーケンス(順序)として操作、観測できる機能を提供するAPIということでしょうか。

またReactiveXは「リアクティブプログラミング」と呼ばれるプログラミング手法を取り入れていると言われています。リアクティブプログラミングの良い例がExcelで「値を入力すると自動で計算結果が変更される」のがポイントになるようです。

導入するメリット

RxSwiftを導入することの大きなメリットはやはり非同期処理やイベント処理などを(シーケンスとして)簡潔に書けるようになることです。 非同期処理を記述する際にコールバック(completionHandler)が増えすぎて可読性が下がったり、APIの結果で処理を分岐したりといった複雑になりがちなコードをスッキリ書けるようになります。

おすすめ記事:【Swift】completionHandlerとは?使い方と@escapingの意味

他にも時間の流れに関する処理が得意だったり、コード自体にまとまりが生まれ、かつ一貫した流れで記述できるので全体の概要が掴みやすくなるのもメリットの1つです。

ストリーム(Stream)とシーケンス(Sequence)とは?

ReactiveXを理解するにはストリーム(stream)を理解する必要がありそうです。そもそもストリームとは日本語で「流れ」を意味する英単語であり、プログラミングの分野ではデータの入出力の流れを保持する抽象的なオブジェクトのことを指します。

ReactiveXでもストリームが登場し、時間の流れの中に「データの変更から完了またはエラーまで」を一連の順序(シーケンス)として保持しています。

その一連の流れはマーブルダイアグラムとして表現されます。左から右に行くにつれ時間の経過を表し、その過程の中でデータの変化などのイベントを検知、最終的には完了またはエラーでストリームは終了します。

RxSwiftとは?導入方法と使い方まとめ!ストリームを理解する

そしてこの考え方こそがリアクティブプログラミングのようです。

Observable

公式リファレンス:Observable

Observableとは日本語で「観測可能」を意味する英単語です。ReactiveXではストリームを観測、検知可能なクラスのことをObservableと呼び、変化やエラー、完了を検知すると通知してくれます。onNextイベントの発生のたびに複数回検知される可能性があり、onErrorまたはonCompletedストリームの中でどちらか1回しか観測されません。そしてそのどちらかを検知したタイミングまたは明示的に指定したタイミングで監視は終了します。

種類 概要
onNext イベントを検知
onError エラーの発生を検知
onCompleted 完了を検知

RxSwiftではObservableクラスとして用意されています。

おすすめ記事:【RxSwift】Observableクラスとは?subscribeメソッドの使い方!

オブザーバーパターン

”「オブジェクトがクラスに依存せずに他のオブジェクトに状態変化を通知するにはどうすればよいか」。この問題をスマートに解決するために考えられたデザインパターンがオブザーバーパターンです。
オブザーバーパターンは監視対象 (サブジェクト/プロバイダー) と観測者 (オブザーバー/リスナー) から成ります。「監視対象のオブジェクトを観測者オブジェクトが監視していて、監視対象が変化したのを観測者が確認したら特定のアクションを起こす」の考え方が基本です。”

引用:Rx入門 (2) - オブザーバーパターン

オブザーバーパターンとは「状態変化などを観測し、通知する」タイプのデザインパターンの一種です。対象となる2つの役割があり「観測される側(Subject)」と「観測する側(Observer)」に分かれます。(Subjectは被写体といった意味です)

おすすめ記事:【GoF】Observerパターンとは?Publish-Subscribeパターンとの違い

ここまでのまとめ

宣言的(Declarative programming)

宣言的と聞くとSwift UIを思い出します。プログラミングには「Declarative Programming(宣言型プログラミング)」と「Imperative Programming(命令型プログラミング)」があります。

「What(何を)」と「How(どのように)」で区別することが多いですがSwift UIとUIKitの2つを見てみると分かりやすいです。

Swift UIでは「こんなボタンを作りたい」と書くだけでボタンが出来上がります。

import SwiftUI

struct TestView: View {
@State    var check = true

var body: some View {
    Button(action: {
      check.toggle()
    }){
      Text(check ? "ON" : "OFF")
    }
}
} 

一方UIKitでは「ボタンを構築して、位置を決めて、タイトルを決めて、アクションを定義して紐づけて...」と1行ずつ処理していく形になります。

import UIKit

class ViewController: UIViewController {

  let button = UIButton()
    
    override func viewDidLoad() {
        super.viewDidLoad()

        let screenWidth = self.view.frame.width
        let screenHeight = self.view.frame.height
                  
        // 位置とサイズ
        button.frame = CGRect(x:screenWidth/4, y:screenHeight/2,
                      width:screenWidth/2, height:60)
        // タイトル
        button.setTitle("ボタン", for:UIControl.State.normal)
        
        // タップされたときの処理
        button.addTarget(self,
              action: #selector(ViewController.buttonTapped(sender:)),
              for: .touchUpInside)
        
        // Viewにボタンを追加
        self.view.addSubview(button)
    }

    @objc  func buttonTapped(sender : Any) {
        print("ボタンをタップされたよ")
    }
}

そしてReactiveXでは宣言的にコードを記述できるようになっているようです。

RxSwiftの導入方法

おすすめ記事:【Swift UI】CocoaPodsのインストール方法と使い方!

RxSwiftを使用するにはライブラリの導入が必要です。今回はCocoa Podsを使ってインストールしていくので「PodFile」に以下の2行を書き込みインストールします。

pod 'RxSwift'
pod 'RxCocoa'
$ pod install                
Analyzing dependencies
Downloading dependencies
Installing RxCocoa (6.5.0)
Installing RxRelay (6.5.0)
Installing RxSwift (6.5.0)
Generating Pods project
Integrating client project

RxCocoa

RxCocoaはRxSwiftの拡張ライブラリでUIKitのビュークラスをRxが使えるようにするためのライブラリです。

これでプロジェクト内でRxSwiftが使えるようになりました。ここからは使用方法を見てみます。

記述方法

RxSwiftでは基本的にメソッドチェーン方式で記述していきます。Swift UIを使っている人なら理解しやすい記述方式だと思います。

なので用意されているメソッドには何かしらの返り値があることが多いです。その返り値に対してさらにメソッドを呼び出し、さらに返り値から…となります。

ここで実際のコードを少しみてみます。

let text = textField.rx.text
        
text.subscribe(onNext: { _ in 
        print("next")
    }, onError: { _ in
        print("error")
    }, onCompleted: { 
        print("completed")
    }).disposed(by: disposeBag)

Observableを観測するためにはsubscribe(購読)メソッドを記述します。このメソッドによりストリームの観測が開始され、その各引数にクロージャーを渡すことで任意の通知が届いた時に処理を行わせることが可能になってます。

データのバインディング

RxSwiftとは?導入方法と使い方まとめ!ストリームを理解する

まずは簡単にUITextFieldの値をラベルにバインディングしてリアクティブに表示させてみます。使用するには上部にimport文を忘れないように記述します。UIKitのビューに対して使用していくのでRxCocoaも必要になります。(そのままコピペで動作します。)

import UIKit
import RxSwift
import RxCocoa

class ViewController: UIViewController {

    let disposeBag = DisposeBag()

    override func viewDidLoad() {
        super.viewDidLoad()
        
        let label = UILabel()
        label.frame = CGRect(x:130, y:200,width:200, height:50)
        label.text = "placeholder..." // textField.text とBindingされるので表示されることはない
        self.view.addSubview(label)

        let textField = UITextField()
        textField.frame = CGRect(x:130, y:300,width:200, height:50)
        textField.borderStyle = UITextField.BorderStyle.line
        self.view.addSubview(textField)

        // Observableクラスの取得
        let text = textField.rx.text
        // ストリームの観測開始        
        text.subscribe(onNext: { [weak self] text in
                label.text = text // 変化した値をUIとリンク
            }, onError: { _ in
                print("error")
            }, onCompleted: {
                print("completed")
            }).disposed(by: disposeBag)
    }
}

ここでポイントになるのはtextFieldから呼び出しているプロパティやメソッドです。

rxプロパティ:Reactive<UITextField>を取得
textプロパティ:ControlProperty<<Optional<String>>を取得
subscribeメソッド:購読
disposed:観測終了用

このコードをマーブルダイアグラムで表すと以下のような感じでしょうか?

RxSwiftとは?導入方法と使い方まとめ!ストリームを理解する

イベントの発生はUITextFieldtextプロパティが変化したタイミングです。そのイベントがObserverに通知され処理(labelのtextに格納)が実行されます。

bind

データをバインディングするためにbindメソッドを使用するともっと簡潔に記述することが可能です。以下もsubscribeを使ったコードと同じ動きをします。

override func viewDidLoad() {
  super.viewDidLoad()
  
  let label = UILabel()
  label.frame = CGRect(x:130, y:200,width:200, height:50)
  label.text = "placeholder..."
  self.view.addSubview(label)

  let textField = UITextField()
  textField.frame = CGRect(x:130, y:300,width:200, height:50)
  textField.borderStyle = UITextField.BorderStyle.line
  self.view.addSubview(textField)
  
  textField.rx.text.orEmpty
  .bind(to: label.rx.text)
  .disposed(by: disposeBag)
}

データバインディングと聞くと双方向を想像しますが、RxSwiftのデータバインディングは双方向ではなく単方向のようです。

おすすめ記事:【RxSwift】DisposeBagクラスの使い方!Disposable

タップイベントを観測する

続いてボタンのタップイベントを観測してみます。基本的な記述方法は先ほどと同じです。以下のコードでボタンをタップするごとにonNextが実行されます。

import UIKit
import RxSwift
import RxCocoa

class ViewController: UIViewController {

    let disposeBag = DisposeBag() 

    override func viewDidLoad() {
      super.viewDidLoad()
    
      let button = UIButton()
      button.frame = CGRect(x: 0, y:0, width: 100 , height: 40)
      button.setTitle("Tap!!", for: .normal)
      button.backgroundColor = UIColor.orange
      button.center = self.view.center
      self.view.addSubview(button)
      
      button.rx.tap
          .subscribe(onNext: { _ in
              print("tap")
          }, onError: { _ in
              print("error")
          }, onCompleted: {
              print("completed")
          }).disposed(by: disposeBag)
}

循環参照

subscribeメソッドのクロージャー内ではselfを強参照してしまい循環参照が発生する可能性があるのでweakまたはunownedを付与して弱参照になるようにしておく

text.subscribe(onNext: { [weak self] text in
              label.text = text // 変化した値をUIとリンク
          }, onError: { _ in
              print("error")
          }, onCompleted: {
              print("completed")
          }).disposed(by: disposeBag)

おすすめ記事:Swift】weakの役割とは?循環参照の意味とARCについて

Operator

RxSwiftではさらにOperatorという概念があります。これは生成されているObservableのイベントを編集や絞り込みを行った新しいObservableを生成する役割を持っています。それ以外にも複数のObservableを結合することも可能です。

Operatorにはたくさん種類があるので使用頻度の高そうなものをいくつか紹介します。

map

これは配列などに使用するmap関数と同じような役割を持ったOperatorです。map関数は配列内の全ての要素に対して任意の処理を実行できる関数でした。ここでのmapも同様に全てのイベントに対して任意の処理(クロージャー)を実行させる役割を持っています。

おすすめ記事:【Swift】map関数の使い方!flatMap/compactMapとの違いと使い方

例えば以下は入力された値がイベントとして渡されるのをUITextFieldの値:\(入力値)という形式に編集して渡しています。

let text = textField.rx.text
        
text.map{ [weak self] text -> String? in
  guard let text = text else { return nil }
  return "UITextFieldの値:\(text)"
}
.subscribe(onNext: { [weak self] text in
        label.text = text
    }, onError: { _ in
        print("error")
    }, onCompleted: {
        print("completed")
    }).disposed(by: disposeBag)

マーブルダイアグラムにすると以下のようなイメージでしょうか?

RxSwiftとは?導入方法と使い方まとめ!ストリームを理解する

記述方法

public func map<Result>(_ transform: @escaping (Element) throws -> Result)
    -> Observable<Result> {
    Map(source: self.asObservable(), transform: transform)
}

一応mapの記述方法も見ておきます。定義は上記のようになっており引数にはクロージャーを渡します。引数のクロージャー内ではイベント発生後に渡される値が受け取れます

今回はUITextFieldのtextプロパティの値を使用して編集した文字列を送るので受け取るための変数と返り値の型を明示的に定義{ text -> String? in ...}」しています。

おすすめ記事:【Swift】クロージャとは?関数との違いとキャプチャの意味

text.map{ [weak self] text -> String? in
  guard let text = text else { return nil }
  return "UITextFieldの値:\(text)"
}

filter

filter配列などに使用する関数と同じ役割のOperatorです。filterを使用することで条件にマッチしたイベントのみにフィルタリングを実装することができます。

おすすめ記事:filterメソッドの使い方

text.filter{ $0!.count > 5 }
.subscribe(onNext: { text in
          label.text = text
      }, onError: { _ in
          print("error")
      }, onCompleted: {
          print("completed")
      }).disposed(by: disposeBag)
RxSwiftとは?導入方法と使い方まとめ!ストリームを理解する

参考文献:公式リファレンス:ReactiveX
参考文献:GitHub:ReactiveX
参考文献:【Swift】RxSwift入門 ①
参考文献:Rx入門 (1) - はじめに
参考文献:RxSwiftについてようやく理解できてきたのでまとめることにした(1)

まだまだ勉強中ですので間違っている点や至らぬ点がありましたら教えていただけると助かります。

ご覧いただきありがとうございました。

searchbox

スポンサー

ProFile

ame

趣味:読書,プログラミング学習,サイト制作,ブログ

IT嫌いを克服するためにITパスを取得しようと勉強してからサイト制作が趣味に変わりました笑
今はCMSを使わずこのサイトを完全自作でサイト運営中〜

New Article

index