6
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

[iOS]いきなり!RxSwift (当方はSwift初心者でいきなりRxSwift!) [基礎編]

Posted at

#はじめに
2012年頃からObjective-C/Javaでスマートフォンアプリ開発を始め、2014年頃からCocos2d-xにハマり、5年間そればかりやってきたら、時代は、Swift/Kotlin、ReactiveやらObserverやら、MVVMやらクリーンアーキテクチャだそうで、完全に取り残されました。どこから始めればよいかよく分からないので、なんとなくRxSwiftから始めてみたいと思います。

#RxSwiftとは
RxとはReactive Extensionsの略で非同期処理やイベント処理などを宣言的に記述することができるライブラリ。
FRP(Functional Reactive Programming)の一種、ReactiveX(C#、Javaなど)ファミリーのひとつでもある。
Rxを使いこなせればありとあらゆるものをストリームとして扱うことが可能らしい。

##リアクティブとは

リアクティブプログラミングとは、通知されてくるデータを受け取るたびに関連したプログラムが反応し(リアクション)して、処理を行うようにするプログラミングの考え方です。

引用元 : 須田智之 (2017/2/16)『RxJavaリアクティブプログラミング』 翔泳社

##ストリームとは
Rxでは、様々な処理をストリームとして扱います。ストリームというのは時間順に並んだ進行中のイベントを表します。以下図はマーブルダイアグラムというもので、横の流れがストリームで、mergeしたりできるよ、ってことでしょう。

スクリーンショット 2019-07-25 15.43.02.png 引用元:[https://rxmarbles.com/](https://rxmarbles.com/)

##RxSwiftで主にできること
・UI イベント受け取り
・Web API レスポンス受け取り
・データの変化の監視

#環境
Xcode10.3
Swift5.0.1
RxSwift 4.3.1
RxCocoa 4.3.1

#準備
1.プロジェクト作成

Xcode > Create a new Xcode project > HelloRxSwift

2.ディレクトリに移動

$ cd HelloRxSwift

3.Podfile作成/編集

$ pod init
$ vi Podfile
Podfile
# Podfile
use_frameworks!
target 'HelloRxSwift' do
	pod 'RxSwift'
  	pod 'RxCocoa'
end

4.ライブラリのインストール

$ pod install

5.定義

ViewController.swift

import UIKit
import RxSwift
import RxCocoa

#ObserverとObservable

####Observer (イベント処理)
Observableからデータストリームを受け取るクラス

####Observable (イベント検知/発生元、ストリーム)
Observableは文字の意味の通りで、監視可能なものを表すクラスを表します。
Observableのイメージは川です。ObserverイベントとしてonNext、onError、onCompleteが流れてきます。
Observableが通知するイベントは以下のようなものがあります。

ObserverType 説明
onNext 通常のイベントを通知する。複数回送れる
onError エラーの発生を通知する。発生後はイベントが一切発生しない
onCompleted 完了を通知する。発生後はイベントが一切発生しない

#Subscribe(購読)とDispose(解除)

####Subscribe
イベントを受け取る側は subscribe メソッドでイベントの購読を行います。
Observableを作成することで川ができ、Subscribeでそこに流れてくるイベントを拾うイメージです。

####Dispose
Disposeは購読を解除(破棄)するためのクラスで、dispose()メソッドを呼ぶことで明示的に購読を破棄するものです。

####DisposeBag
DisposeBagは、Disposeインスタンスを貯め、自身が解放(deinit)されたときに管理している購読を全て自動で解(unsubscribe)します。いい感じで購読を破棄して、メモリーリークを回避してくれます。

ViewController.swift

import UIKit
import RxSwift
import RxCocoa

class ViewController: UIViewController {

    private let disposeBag = DisposeBag()
    private var viewModel: HogeViewModel!
    
    override func viewDidLoad() {
        super.viewDidLoad()

        viewModel = HogeViewModel()
        viewModel.helloWorldObservable
            .subscribe(onNext: { [weak self] value in
                print("value = \(value)")
        })
        .disposed(by: disposeBag)
        
        viewModel.updateItem()        
    }
}

class HogeViewModel {
    
    let helloWorldSubject = PublishSubject<String>()

    var helloWorldObservable: Observable<String> {
        return helloWorldSubject.asObservable()
    }
    
    func updateItem() {
        helloWorldSubject.onNext("Hello World!")
        helloWorldSubject.onNext("Hello World!!")
        helloWorldSubject.onNext("Hello World!!!")
        helloWorldSubject.onCompleted()
    }
}

#SubjectとRelay
・Subject > 通信処理やDB処理など、エラーが発生したときにその内容によって処理を分岐させたいときに使う
・Relay > RelayはSubjectの薄いラッパー定義。UIに値をBindする時に使います。.nextイベントを流すにはacceptメソッドを使います(onNextのみ流れることが保証されている場合、Relayを使うのが適切、onErrorなどが発生すると止まってしまうため)

SubjectやRelayはprivateとして定義することで、クラス外からのイベント発生を防ぎ、デバッグ困難化、アプリ肥大化を抑制できる。

クラス名 説明 イベント バッファ
PublishSubject 一切キャッシュしないSubject onNext, onError, onComplete 持たない
BehaviorSubject 直近の値を1つだけキャッシュするSubjectで、初期値を与えることができる。 onNext, onError, onComplete 持つ
PublishRelay 初期値なし、valueプロパティなし onNext 持たない
BehaviorRelay 初期値あり、valueプロパティあり、 onNext 持つ

バッファ
BehaviorSubject/Relayは、subscribe時に1つ過去のイベントを受け取ることができる。最初にsubcribeするときは、宣言時に設定した初期値を受け取る。

例:

let hogeSubject = PublishSubject<String>()
let hogeRelay = PublishRelay<String>()

hogeSubject.onNext("ほげ")
hogeRelay.accept("ほげ")

#bindとOparator

####bind
指定したオブジェクトに対し、イベントストリームを接続できます。RxSwiftでは単方向バインディング。振る舞いは、subscribeして値をセットすることとほぼ同じ。

ViewController.swift
import RxSwift
import RxCocoa

//...

private let disposeBag = DisposeBag()

//...


let label = UILabel()
label.frame = CGRect(x: 50, y: 75, width: 200, height: 21)
view.addSubview(label)

let textField = UITextField()
textField.frame = CGRect(x: 50, y: 150, width: 200, height: 21)
textField.placeholder = "入力してください"
view.addSubview(textField)

textField.rx.text
.bind(to: label.rx.text)
.disposed(by: disposeBag)

####Operator
Operatorは、Observableに対してイベントの値の変換、絞り込みなどの加工を施して、新たにObservableを生成する仕組みです。また、ふたつのObservableのイベントを合成・結合することなども可能です。

Observable+StandardSequenceOperators

オペレータ 説明
filter 指定条件に合致するものだけ通過
takeWhile 先頭から指定条件に合致している間だけイベントを通知
takeWhileWithIndex takeWhile のインデックス付きバージョン
take 先頭から指定個数だけ通知して完了
takeLast 最後から指定した数を通知して完了
skip 先頭から指定した数だけイベントをスキップ
skipWhile 先頭から指定条件に合致している間だけイベントをスキップ
skipWhileWithIndex skipWhile のインデックス付きバージョン
map 各要素の内容を指定した処理で別の値や型に変換します。
mapWithIndex map のインデックス付きバージョン
flatMap イベントを Observable に変換した上で、 それを並列実行して発行するイベントをマージ( map + merge )。
flatMapWithIndex flatMap のインデックス付きバージョン
flatMapFirst イベントを Observable に変換して順次実行するが、実行中のものが完了するまでに来た Observable は無視
flatMapLatest イベントを Observable に変換し、常に最新の Observable を利用( map + switchLatest )。
elementAt 指定インデックス番号のイベントを通知して完了
single 1つだけしかイベントを発行しないことを保証

Observable+Multiple

オペレータ 説明
combineLatest 複数の Observable の最新値同士を組み合わせる
zip 複数の Observable の内容を順番に組み合わせる
switchLatest 次の Observable が来たらそちらにスイッチする
concat 複数の Observable を完了するまで待って順次 subscribe する
merge 複数の Observable を subscribe して、全てのイベントをマージして通知
catchError エラーを補足して復旧
catchErrorJustReturn エラーを捕捉して復旧し、引数に与えた値を代わりに通知
takeUntil 指定した Observable が何かイベントを発行すると終了
skipUntil 指定した Observable が onNext を発行するまで、元の Observable が発行したイベントを無視
amb 複数の Observable のうち、最初にイベントを発行した Observable を採用
withLatestFrom 元の Observable に指定した Observable の最新値を合成

Observable+Time

オペレータ 説明
debounce 指定期間新たなイベントが発行されなくなってから最後に発行されたイベントを発行
throttle 指定期間の計測が開始された最初のイベントと、指定期間新たなイベントが発行されなくなってから最後に発行されたイベントを発行
sample 引数に渡した Observable の発行タイミングで元の Observable の最新値を通知
interval 指定期間ごとにイベントを発生
timer 指定時間後にイベントを発生させて完了
take 先頭から指定時間の間のイベントだけ通知して完了
skip 先頭から指定時間の間のイベントだけ無視してスキップ
ignoreElements 全てのイベントを無視して完了やエラーのみ通知
delaySubscription subscribe するまでの時間を遅らせる
buffer 最大指定時間または最大指定個数ごとにイベントを配列にまとめて通知
window 最大指定時間または最大指定個数ごとにイベントを Observable にまとめて通知
timeout 指定時間イベントが通知されなければエラー

Observable+Creation

オペレータ 説明
create 任意の処理を行う Observable を生成
empty onCompleted を発行するだけの Observable を生成
never onError, onCompleted も含めて一切のイベントを発生させない Observable を生成
just 指定した要素を1回通知して完了する Observable を生成
error onError を発行するだけの Observable を生成
of 引数で渡した要素が順に通知される Observable を生成
deferred subscribe されるたびに Observable を新しく生成
generate for文っぽい初期値と停止条件、生成処理を指定して Observable を生成
repeatElement 指定された要素を永遠に通知し続ける Observable を生成
using Observable と、それと同じ生存期間を持つリソースとを紐付け
range 指定範囲の値を順に流す Observable を生成
toObservable 配列などのシーケンス型を Observable に変換

#サンプルアプリ

TextFieldに文字を入力すると、bindされたLabelに文字が出力されるサンプルです。

ViewController.swift

import UIKit
import RxSwift
import RxCocoa

class ViewController: UIViewController {

    private let disposeBag = DisposeBag()
    private var viewModel: HogeViewModel!

    override func viewDidLoad() {
        super.viewDidLoad()

        //ラベル
        let label = UILabel()
        label.frame = CGRect(x: 50, y: 75, width: 200, height: 21)
        view.addSubview(label)
        
        //テキストフィールド
        let textField = UITextField()
        textField.frame = CGRect(x: 50, y: 150, width: 200, height: 21)
        textField.placeholder = "入力してください"
        view.addSubview(textField)
        
        //textFieldのテキスト文字数を数えてLabelのテキストへ反映
        textField.rx.text.map{text -> String? in
            guard let text = text else { return nil}
            return "\(text)(\(text.count)文字)"
        }
        .bind(to: label.rx.text)
        .disposed(by: disposeBag)
    }
}

##動作イメージ
rxswift1.gif

#最後に

#おまけ
####Observable+Binding

オペレータ 説明
multicast 指定した Subject を使った ConnectableObservable を返す
publish PublishSubject を使った ConnectableObservable を返す
replay 指定数をキャッシュする ReplaySubject を使った ConenctableObservable を返す
replayAll 全てのイベントをキャッシュする ReplaySubject を使った ConnectableObservable を返す
refCount ConnectableObservable の connect を subscribe の数で参照カウント管理
share キャッシュしない Cold-Hot 変換 = publish + refCount
shareReplay キャッシュ付きの Cold-Hot 変換 = replay + refCount
shareReplayLatestWhileConnected shareReplay(1) の最適化バージョン

####Observable+Concurrency

オペレータ 説明
observeOn イベントを受け取るスケジューラを指定
subscribeOn subscribe を実行するスケジューラを指定

####Observable+Debug

オペレータ 説明
debug 流れてくるイベントを標準出力に print

####Observable+Single

オペレータ 説明
distinctUntilChanged 変化がない間はイベントをスキップ
doOn パイプラインの途中に処理を挟み、イベントはそのまま通過させる
startWith 先頭に指定した値を発行するイベントを付け加える
retry エラーが発生したら再試行
retryWhen retry のもっと細かい制御ができる汎用バージョン
scan reduce の途中経過もイベント発行するバージョン

####Disposables

クラス 説明
AnonymousDisposable dispose が呼ばれたときに実行するアクションを指定する
BinaryDisposable 2つの Disposable をまとめる
BooleanDisposable dispose されたかどうかを保持するだけ
CompositeDisposable 一緒に dispose される Disposable をグループ化
NopDisposable dispose されても何もしない
RefCountDisposable 参照カウントで管理する
ScheduledDisposable 指定したスケジューラで dispose 処理をする
SerialDisposable 新しいものが設定されると古いものを dispose して置き換える
SingleAssignmentDisposable 1度だけ内部の Disposable を設定できる
StableCompositeDisposable BinaryDisposable を返すだけ
DisposeBag 追加されたものを自身が解放されるときにまとめて dispose する

####Schedulers

クラス 説明
ConcurrentDispatchQueueScheduler dispath_queue_t や QOS で指定する並列実行
ConcurrentMainScheduler subscribeOn に最適化されたメインスレッド指定
CurrentThreadScheduler 現在のスレッドで処理をキューイングして順次実行
HistoricalScheduler NSDate, NSTimeInterval を使う VirtualTimeScheduler
ImmediateScheduler 現在のスレッドで処理を即時実行
MainScheduler observeOn に最適化されたメインスレッド指定
OperationQueueScheduler NSOperationQueue を使った非同期実行
SerialDispatchQueueScheduler dispatch_queue_t や QOS で指定する順次実行
VirtualTimeScheduler 時間の経過をプログラムで制御できる

#参考
比較して学ぶRxSwift入門 (技術の泉シリーズ(NextPublishing))

【iOS, RxSwift】RxSwift入門〜オブザーバーパターンのイメージ〜

RxSwiftの機能カタログ

6
8
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?