Edited at

SwiftでReactive Programming

More than 3 years have passed since last update.


はじめに

ここ数年で注目を集めて続けているReactive Programmingですが、Swiftにもライブラリとして提供されていたり、実際にサービスにも多く導入されていることから、Reactiveのことからライブラリまで調べてみました。今回はじめてReactiveに入門したので何か指摘点等あれば気軽にコメント頂けると幸いです。


概念から学ぶ

まずは、Reactiveの概念中心の内容になります。

初めにサンプルコードから学ぶを軽く見た方がイメージが湧きやすいかもしれないです。


Reactiveとは

Reactiveというキーワードは特にエンジニアなら聞いたことはあるかと思いますが、Reactiveという概念自体、Reactive Applicationと、Reactive Programming二つの意味を指すことが多いようです。


Reactive Application

まずReactiveの定義ですが、Jonas BonerとBruce Eckelによって発表されたThe Reactive ManifestoにReactiveというアプリケーションはどのようなものか説明がされています。


we want systems that are Responsive, Resilient, Elastic and Message Driven. We call these Reactive Systems.

(一部抜粋)


即応性、耐障害性、弾力性、メッセージ駆動を備えたシステムをReactive Systemsとして定義してます。障害が起きても落ちることがなく、高い即応性を保ち続けることでユーザが期待するインタラクティブなUX(ユーザー体験)を提供するシステムのことを指しているようですね。すなわち、ここで言われているReactiveとはユーザが期待する即応性を保ち続ける思想のことを意味しているようです。近年、アプリケーションで高いUXが追求されきていることからも、今後Reactive Systemsの重要性は高まっていくと考えられます。


Message Driven(メッセージ駆動)

イベントごとに非同期的に送受信を行うイベント駆動型であることで、次の3つの性質を生む



1. Responsible(即応性)

迅速かつ一貫した応答時間を提供する



2. Resilient(耐障害性)

障害に直面しても即応性を保ち続ける



3. Elastic(即弾力性)

ワークロード(負荷)が変動しても即応性を保ち続ける




Reactive Programming

Reactive Applicationを実現するために用いられるReactive Programming(以降RP)ですが、Wikipediaでは次のように述べられています。


In computing, reactive programming is a programming paradigm oriented around data flows and the propagation of change. This means that it should be possible to express static or dynamic data flows with ease in the programming languages used, and that the underlying execution model will automatically propagate changes through the data flow.

英語版Wikipedia / Reactive programming


つまりRPとは、時間と共に変化する(動的な)値とそれに対する関係性を記述してプログラミングを行うパラダイムになります。RPを用いることで値の状態が変化した時に自動的に値が変更されることになります。


オブジェクト思考における状態管理

オブジェクト指向型プログラミングでは、状態の依存関係がある場合(値が変更された時に、他の値やオブジェクトを更新しなくてはならないなど)、初期化やObserverパターンを用いた状態更新のロジック、必要であればキャッシュのロジックなどを記述する必要があります。Swiftでは、NSNotificationCenterを用いたObserverパターンがありますが、一つ一つのイベントに対してremoveObserver()などでメモリを解放する必要がり、バグの温床にもなりかねないですね。

RPではこれらの状態更新やキャッシュが自動的に行われるため、私たちは状態が変更された場合にどのように振る舞うか宣言的な式を記述するだけになるのです。


RPとエクセルの表計算

RPの例えとしてよく使われるのがエクセルの表計算です。以下のシートのA3, B3にはそれぞれ=A1+B1, =A1*B1が入力してあります。

=A1+B1, =A1*B1という関係式が記述されてるだけで、A1B1の値を変更すると自動的に値が変更されているのがわかるかと思います。これこそまさにRPが実現できていると言えますね。


Functional Reactive Programming

RPを関数型プログラミング実装にさせたのがFunctional Reactive Programming(以降FRP)と言われるものです。


FRP is programming with asynchronous data streams

【翻訳】あなたが求めていたリアクティブプログラミング入門


FRPはよく非同期データストリームを用いるプログラミングであると言われることからも、FRPを理解する上でデータストリームの概念をつかむことが重要とされています。ストリームは、変数、ユーザー入力、プロパティ、キャッシュ、データ構造など何に対してでも表現することができます。

さて、先ほどRPとは時間と共に変化する(動的な)値とそれに対する関係性を記述してプログラミングを行うパラダイムと説明しましたが、時間と共に状態が変化するもの(データストリーム)はMarble Diagramsという図形で表現されます。以下の画像は最も単純なクリックイベントのストリームです。


引用: The introduction to Reactive Programming you've been missing

横軸が時間の流れで、が送信されるデータになります。画像では、がクリックのイベントを表し、×がエラー、|が完了処理を表しています。それぞれの関数を定義することによって非同期にキャプチャすることができます。これはAPIなどのコールバック処理をイメージしていただくとわかり易いかと思います。

このストリームは公式にObservableと呼ばれ、このObservableを監視することをsubscribeと言います。

【翻訳】あなたが求めていたリアクティブプログラミング入門の記事の何回クリックされたかを表すカウンターストリームを作る例がFRPの本質を表していると思いましたので、紹介していきたいと思います。

先ほどのストリームですが、時間軸に沿って捉えると[click1, click1, ..]のようなイベントのリストとして捉えることができます。そして、リストの処理であれば関数型プログラミングでアプローチすることができますね。実際にこのストリームに対してRx系のライブラリではmapfilterなどの関数が用意されています。以下の画像が何回クリックされたかを表すカウンターストリームを作る例です。


引用: The introduction to Reactive Programming you've been missing

まず、画像で表されている関数はオペレータメソッドと呼ばれ、返り値として処理が施された新しいストリームを返します。返されたストリームは同じくリストとして捉えることのできるストリームなので、再度オペレータメソッドが適応できます。

画像のオペレータメソッドはデモ用なので詳しいことは考えなくてもいいですが、初めにクリックをリストとして積み上げ(buffer(stream.throttle(250ms)))、そのストリームのリストをクリック数として変換し(map)、さらに返されたストリームに対して、1の場合を無視することで(filter(x >= 2))、最終的な2回以上クリックしたイベントを取得するストリームが手に入っていますね。最後にこのストリームに対してsubscribeすることで望むように反応してくれる処理が書けるわけです。

実際にはこの処理は

stream

.buffer(関数)
.map(関数)
.filter(関数)
.subscribe(処理)

のような関数型言語のPromiseパターンのようなアプローチができます。このようにRPは関数型言語と親和性が高いものとなっているわけです。これが関数型言語でもあるSwiftにRxが導入された背景にもなっていると考えられます。

これらのストリームとオペレータメソッドはRxMarblesで視覚的に見ることができるので各オペレータメソッドはここで確認するとわかりやすいと思います。


RxSwift

RxSwiftとは、Reactive ExtensionsをSwift実装として対応させ、SwiftでRPを実現することができるライブラリです。


Reactive Extensions

Reactive Extensions(以降Rx)は2009年に実験的にマイクロソフト社が開発したライブラリで、2011年に正式な製品として認められました。

C#の3.0から導入されたLINQ (Language-Integrated Query)を発展させたものであり、従来では手間がかかる非同期・イベント処理や時間が関連する処理をLINQの形式で宣言的で簡潔に記述することが可能になっています。


RxCocoa

UIKitのクラスをプロパティやイベントをRxが使えるようにWrapしているライブラリで、Viewなどの状態変化やイベントなどを簡単にBindingすることができます。そのためRxSwiftと合わせて使われることが多いです。


RxSwift導入にあたり

複雑になりがちな非同期・イベント・時間に関する処理を簡潔かつ宣言的に記述できることで、コード量が削減できることや、可読性が高まることがRxSwiftのメリットですが、RPを実現するライブラリはいくつかあります。

その中でRxSwift導入を検討する一番の理由として、Rx自体RxJavaやRxJSを始め様々な言語に対応されていることだと思います。(ReactiveX/Languages)

そのためRxで使われる概念は他言語でも共通なものが多く、他言語の情報を含めて大量の知見を得られることや他言語でも知見を生かすことができることから学習に対するリターンが大きいと思います。


サンプルコードから学ぶ

実際にRxSwiftを用いて、動作確認していきたいと思います。

RxSwiftのExamplesの簡単な計算式を例に説明していきます。GoFのオブザーバーパターンを理解していると読みやすいかと思います。

まずは、RxSwiftを使ってない簡単なサンプルです。

var c = ""

var a = 1
var b = 2

if a + b >= 0 {
c = "\(a + b) is positive"
}

a = 4
print(c)//"3 is positive"

通常だとaの変更が反映されずに、宣言時のabの値が反映されて3 is positiveと出力されます。動的に値が変更される為には、KVOなどのObserverパターン実装などが必要になりますね。今度は、RxSwiftを用いて簡単な式を記述してみます。

import RxSwift

let a = Variable(1)
let b = Variable(2)

let c = Observable.combineLatest(a.asObservable(), b.asObservable()) { $0 + $1 }
.filter { $0 >= 0 }
.map { "\($0) is positive" }
.subscribeNext { print($0) }//"3 is positive"

a.value = 4//"6 is positive"
b.value = 6//"10 is positive"
b.value = -8// doesn't print anything

abの値が変更されると、自動的にabの値が更新されたcが出力されるようになっています。filtermapなどのオペレータメソッドによって、a + bが0以上の場合のみ、subscribeNextの処理が施されるようになってますね。それではそれぞれの処理を順次解説していきたいと思います。

SubjectsはObserverパターンでいう監視される側という意味になります。Variable()というクラスを用いることで、abの値が監視される対象になっています。

let a = Variable(1)

let b = Variable(2)

Variable()BehaviorSubjectのWrapperクラスでSubjectsクラスになります。Genericsで定義されているので、Variable<Int>(1)のような記述も可能です。BehaviorSubjectと違って終了処理やエラー処理がないSubjectsクラスなので、単に値をObserveしたい時に使います。続いて、次の処理を見ていきます。

let c = Observable.combineLatest(a.asObservable(), b.asObservable()) { $0 + $1 }//Observableを返している

.filter { $0 >= 0 }//combineLatestを参照し、新しいObservableを返す
.map { "\($0) is positive" }//filterを参照し、新しいObservableを返す
.subscribeNext { print($0) }//mapを参照し、新しいObservableを返す

この処理で使われているオペレータメソッドを見ていきましょう。


オペレータメソッド


  • combineLatest

    二つのObservable(値)に対して、処理が施された最新のObservable(値)を返す。


  • filter

    条件を満たしたObservable(値)のみを返す


  • map

    処理が施されたObservable(値)を返す


オペレータメソッドが新しいObservableを生成して返し、次のオペレータメソッドがそれを参照し新しいObservableを返しています。Observableはsubscribeできるオブジェクトのことで、それぞれのオペレータメソッドはObservableとObserverの役割を担っています。

このように要素の加工やフィルタリングの処理が順々と行われていく、Promiseパターンのような記述ができるようになっています。これらの処理の流れやObservableについての詳細はRxSwift 読んでるの記事がわかりやすいかと思います。

subscribeNextAnonymousObserverというObserverを生成していて、一番新しい値を購読するので、最初の宣言時に初期値として"3 is positive"が出力されています。

このように、変化した時の振る舞いを記述することで、監視されたabの値が更新されたタイミングで、csubscribeNextの処理が反応するようになっています。


RxCocoaを用いたサンプル

UIKitを用いて、DataBindingをする場合の例を紹介していきます。UIKitを用いてRxを実装していく場合にはRxSwiftと合わせて、RxCocoaをimportする必要があります。

import RxSwift

import RxCocoa
...

@IBOutlet weak var textField: UITextField!
@IBOutlet weak var label: UILabel!

let disposeBag = DisposeBag()
...

textField.rx_text
.subscribeNext { [unowned self] text in
self.label.text = text
}
.addDisposableTo(disposeBag)

RxCocoaが定義しているrx_textがObserverの役割を担っていて、subscribeNextでBindingしています。

ここのDisposeBag()は、deinitのタイミングでDisposableを解放させるためのインスタンスで、画面破棄などのタイミングで自動的にDataBindingが解除されるようになっています。addDisposableTo(disposeBag)と記述することで、DisposeBagがObserverのインスタンスを管理してくれています。

また、subscribeNextの戻り値であるDisposableインスタンスに対して、dispose()を呼び出せば、明示的にDataBindingを解除することもできますが、特に必要がない場合は、DisposeBagを用いた自動的なDataBindingの解除がベストだと思います。

以下、UIButtonやUISliderを用いた例になります。基本的な処理はほとんど同じです。


Buttonのタップ検出

import RxSwift

import RxCocoa
...

@IBOutlet weak var button: UIButton!
let disposeBag = DisposeBag()
...

button.rx_tap
.subscribeNext { _ in
print("tapped!")
}
.addDisposableTo(disposeBag)


Sliderの値のBinding

import RxSwift

import RxCocoa
...

@IBOutlet weak var slider: UISlider!
@IBOutlet weak var label: UILabel!

let disposeBag = DisposeBag()
...

slider.rx_value
.map { String($0) }
.subscribeNext { [unowned self] text in
self.label.text = text
}
.addDisposableTo(disposeBag)


おわりに

Reactiveからライブラリの事まで簡単にまとめましたが、ReactiveやRxの事を深く理解するにはまだまだ学ぶことが多く、学習コストは高いと感じました。

しかし、概念の事を学ばなくてもRxSwiftなどのライブラリ使えるので、実際にライブラリを使ったり、サンプルコードを読んで行きながら学ぶことが重要かなと思いました。これからも、RxやReactiveのまとめを記事にしていけたらなと思います。


参考


RxSwift


Reactive Extensions


Reactive