この記事について
この記事は、Conquering ReactiveSwift: Primitives (Part 2)の翻訳です。
以下本文です。
ReactiveSwiftを克服する: 基本要素 (Part 2)
ReactiveSwiftを克服するシリーズのパート1では、命令型プログラミングと関数型リアクティブプログラミングで問題を解決する方法の違いを研究しました。この記事からはReactiveSwiftの核心にせまっていきます。まずはReactiveSwiftの種々の基本要素から始めていきます。
さあ始めましょう
ReactiveSwiftの制作者たちはこう言っています:
ReactiveSwiftは組み立てやすく、宣言的で柔軟な基本要素を提供します。そして、基本要素は時間をまたいだ値のストリームという基礎概念に基づいて作られています。
ReactiveSwiftは関数型リアクティブプログラミングを可能にする強力な基本要素を数多くもっています。それらの基本要素を理解するにはそれぞれを役割によってカテゴリに分類するのが良いでしょう。下の画像にまとめました:
訳注: 画像は翻訳元から引用しました。Primitiveは「基本要素」という意味です。
Source
前回の記事で議論した通り、リアクティブプログラミングでは、その時々の状態ではなく、システムを時間に沿った一連の変化に対して設計するのでした。Sourceカテゴリに属する基本要素の責務はこの一連の変化の生成や伝搬です。Sourceカテゴリの基本要素にはEvent、Signal、SignalProducer、Action、Propertyがあります。
1. Event
Eventは何か起こったことを示します。ストリームを流れる基本単位がEventです。Eventには4種類あります:
- Value: 有効な値をともなうイベントです。
- Failed: エラーを意味するイベントです。
- Completed: ストリームの終わりを意味します。Completedの後には他のイベントは続きません。
- Interrupted: イベントの生産が遮断されたことを意味します。
2. Signal
Signalは一方通行のEventのストリームです。Signalを流れるEventはObserverから購読することができます。Signalを監視しても、Signalへは副作用は起こりません。あるSignalからのEventは、それを監視しているObserverに一度に送られます。
3. SignalProducer
名前の通り、SignalProducerはSignalを生産します。SignalProducerは開始を遅らることのできるタスクをカプセル化します(さらに繰り返すことも可能です)。つまり、SignalProducerは遅延実行したり繰り返し実行することのできるタスクをカプセル化します。SignalProducerがstartすると新たなSignalが生産されます。こうして生産されたSignalが排出する値はカプセル化されたタスクの実行結果です。このように、SignalProducerはまずstartする必要があるのでcoldであると言われます。一方でSignalはwarmと言われます。
4. Action
Actionもタスクを遅延実行したり繰り返し実行したいときに使います。ActionはSignalProducerのタスクをカプセル化したもので、Actionの方が高度な制御が可能です。たとえば、インプットからアウトプットを制御したり、動作可能/不可能を切り替えたりすることも可能です。さらに、Actionの状態をProperty(後で登場します)からリアクティブに制御することもできます。インプットを引数にActionを実行すると、Actionはそのインプットと自らの状態をタスクにapplyして、アウトプットをObserverに渡します。
5. Property
Propertyは監視可能なボックスで、値を保持します。保持している値に変更があればObserverに通知します。SignalProduerとSignalを返すゲッターが定義されているので、そこから値の変更を監視することができます。プロパティは、初期値やSignal、SignalProduer、あるいは他のPropertyのいずれかを通して初期化します。
下の画像はさまざまな基本要素が互いに関係していることを表しています。
訳注: 画像は翻訳元から引用しました。
Consumer
Consumerカテゴリに属する基本要素はSourceから排出されたEventにしたがって何らかの反応をします。このカテゴリには2つの基本要素があります。ObserverとMutablePropertyです。
1. Observer
Observerは排出されたEventに対して行うタスクをカプセル化します。Eventを引数にとるクロージャの薄いラッパーでもあります。
2. MutableProperty
MutablePropertyは監視可能なボックスで、値を保持します。この点はPropertyと同じです。SignalProducerとSignalのゲッターがある点も同様です。しかし、Propertyと異なるのは保持した値を直接変えることができるところです。また、BindingTargetProviderプロトコルに準拠しているのでSignal、SignalProducer、あるいはPropertyから< ~
(バインディング演算子)を通して値を更新することができます。
Operators
OperatorsはConsumerがSourceをどう受け取るのかを規定します。ReactiveSwiftには副作用、変換、平滑化などなど豊富なOperatorがあります。次回以降の記事で解説します。
Scope
このカテゴリに属する基本要素はSourceとConsumerとの相互作用の有効期限を決定します。DisposableとLifetimeがこのカテゴリに含まれます。
1. Disposable
Disposableはメモリ管理とキャンセル処理の仕組みです。Signalの監視を開始するとDisposableが返ります。このDisposableを解除する(つまりdisposeする)と、Observerは監視していたSignalからのイベントを受け取らなくなります。監視を解除してもSignalには影響はありません。SignalProducerはSignalを生成するとDisposableを返します。これは生産されたSignalをキャンセルするために使います。
2. Lifetime
Lifetimeはオブジェクトの有効期限を表します。Lifetimeは、あるUIパーツが存在している間だけ他からの通知を監視したい場合など、監視の対象がObserverより長く存続するようなときに役立ちます。
組み合わせて使う
では、ここまで見てきた基本要素の使い方を理解していきましょう。次の例を考えてみます。
textField
に入力されたテキストが10文字以上の時だけbutton
のisEnable
をtrue
にする
Step 1: Signalを定義する
let signal = textField.reactive.continuousTextValues
textField
に入力されたテキストのSignalはtextField.reactive.continuousTextValues
と書きます。これはReactiveCocoaの一部で、ReactiveSwiftのものではありません。ReactiveCocoaはCocoaフレームワークの種々の機能をラップして、ReactiveSwiftの基本要素を提供してくれるものです。
Step 2: Signalを変換する
さきほど定義したSignalはオプショナルなString(つまりOptional<String>
)を排出します。map
というOperatorを使ってBool型の値を排出するSignalに変換しましょう。
let transformedSignal = signal
.map { text in
text ?? "" }
.map { text in
text.count > 10
}
Step 3: Signalを監視する
次に、Signalを監視してbutton
のisEnabled
を切り替えましょう。いまから定義するのは<Bool, NoError>
型のObserverなので、<Bool, NoError>
型のSignalあるいはSignalProducerからの値を受け取ることができます。Observerが定義できたら、observe(:_)
で監視を開始しましょう。
let observer = Signal<Bool, NoError>.Observer(value: { value in
button.isEnabled = value
})
let disposable = transformedSignal.observe(observer)
Step 4: Signalの監視を解除する
observe
はDisposable型のインスタンスを返します。このインスタンスは監視を解除するためにつかいます。
disposable?.dispose()
クラスのデイニシャライザで全てのObserverの監視を解除するのがベストプラクティスの一つです。
コード全体はこうなっています。
// consumerを定義
let observer = Signal<Bool, NoError>.Observer(value: { value in
button.isEnabled = value
})
// sourceを定義
let signal = textField.reactive.continuousTextValues
let transformedSignal = signal
.map { text in
text ?? "" }
.map { text in
text.count > 10
}
// signalを受け取る
let disposable = transformedSignal.observe(observer)
// Scopeを制限する
disposable?.dispose()
参考情報
こちらのリンクからさらに知識が得られます。
https://github.com/ReactiveCocoa/ReactiveSwift/blob/master/Documentation/ReactivePrimitives.md
まとめ
この記事では、ReactiveSwiftの種々の要素と、それらを使って関数型リアクティブプログラミングをどう実践するかみてきました。サンプルコードはこちらにあります。次の記事では、Signalについて詳しく議論してきます。
最後まで読んでいただきありがとうございます .