はじめに
-
Observable
ってなんなん? -
subscribe(onNext:)
の「購読」ってどういう意味? -
bind
?subscribe
?違いがようわからん…。
RxSwift の基本である「Observable」は、配列(Sequence)とは似て非なるもの。特に「非同期で値が流れてくる」「処理がイベントで発火する」といった特性にピンと来ていないと、コードだけ見ても理解が追いつきません。
この記事では、物流倉庫とコンベア作業 を例にして、Observable の仕組みと subscribe の意味を図解で徹底的に分かりやすく書いてみました。
全くの初心者なので、間違いがある場合があります。
初心者・MVVM学習中の私に、間違いがあればご指摘ください。
Observable
ってなんぞや
Observable
はシーケンス操作のためのクラスです。
シーケンス操作というと Array
と for-in
の組み合わせを連想する人が多いのではないでしょうか。swift
の標準ライブラリには、Sequence
というプロトコルがあります。
実は、RxSwift
の Observable
とUIKit
の sequence
は、値の処理方法が違えど設計思想は同じなのです。
let array = [1,2,3,4,5]
for value in array{
print(value)
}
これは標準ライブラリの Sequence
プロトコルを用いて、配列を用意し、for-in
を使って値を一つずつ取り出してprint処理しています。
この処理は、RxSwift
の Observable
を使って同じ処理をすることができます。
let observable = Observable.of(1,2,3,4,5)
let subscription = observable.subscribe(onNext: { value in
print(value)
})
何気に書いているfor-in
ですが、内部的には以下のように処理されています。
let array = [1,2,3,4,5]
let seq = array.makeIterator()
print(seq.next()) // 1
print(seq.next()) // 2
print(seq.next()) // 3
print(seq.next()) // 4
print(seq.next()) // 5
iterator
というワードが出てきました。
これは、配列などの集合体の要素を順番に処理するための仕組みのことです。
[1,2,3,4,5]
という要素が入った ただの配列 を、要素ごとに1つずつ処理できる 仕組み に変えるのが、makeIterator()
です。
このmakeIterator
ですが、実はObservable
のsubscribe
も同じ役割をしています。
let observable = Observable.of(1,2,3,4,5)
まず、observable
は、現時点では let array = [1,2,3,4,5]
と同じと考えていただいて差し支えありません。(ストリームであるという違いがありますが、今は配列と考えてください。違いは必ず後述します。)
let subscription = observable.subscribe()
配列であるobservable
にsubscribe
メソッドを呼ぶことで、配列の要素(厳密には違う)を順番に処理するための仕組みに変換することができます。
つまり、Sequence
の makeIterator()
と Observable
の subscribe()
は、両者とも配列の要素を順番に処理するための仕組みに変換しているという点で同じなのです。
このsubscribe
メソッドですが、onNext
という引数があります。これはクロージャです。
配列を一つずつ順番に取り出して(subscribe)、その要素をonNext
から始まるクロージャで使用することができるのです。
let subscription = observable.subscribe(onNext: { value in
// ここで、observableという配列から要素を1つ取り、それを使用する処理を実装できる。
})
これは、for-in
の
for value in array{
// ここで、arrayという配列から要素を1つ取り、それを使用する処理を実装できる。
}
とよく似ています。
つまり、
let array = [1,2,3,4,5]
for value in array{
print(value)
}
と
let observable = Observable.of(1,2,3,4,5)
let subscription = observable.subscribe(onNext: { value in
print(value)
})
は、配列から要素を取り出して、要素ごとに処理を行うという同じ処理なのです。
では、なぜ同じ処理なのに、Observable
が存在しているのか。
結論から言うと、「非同期」的な処理に向いているからです。
「非同期」とシーケンス
上記で、Observable
とSequence
は同じであると書きましたが、設計思想が同じなだけで、値の処理方法全く違います。ごめんなさい。
① sequence
+for-in
の場合、
- 処理したい要素を自分でかき集めて(プル方式)
- 一度に処理する
同期的な処理 に使用します。
② Observable
の場合、
- 要素が第三者から不定期に送られ(プッシュ方式)
- 処理したい要素が届くのを待つことができる
非同期的な処理 に使用します。
すこしわかりずらいですよね。
前項での間違いを訂正しながら、かつ物流倉庫を例にして解説します。
その前に、前項の間違いを正させてほしい。
let observable = Observable.of(1,2,3,4,5)
前項で、
observable
は、現時点ではlet array = [1,2,3,4,5]
と同じと考えていただいて差し支えありません。
としていました。忘れてください。
Observable.of(1,2,3,4,5)
は配列ではなく、ストリームなのです。
物流倉庫に例えて、再度解説し直します。
Observable.of(1,2,3,4,5)
登場するプロパティ・メソッドは以下のようなイメージをしてください。
-
Observable
: コンベア・ラインのようなものを用意する。
-
.of(1,2,3,4,5)
: 1,2,3,4,5 という荷物をコンベア・ラインに流す。
つまり、observable
プロパティは 1,2,3,4,5 という荷物が流れるコンベアそのもの です。
以下のようなイメージです。
このコンベア・ラインを、Rx
的に言い換えると ストリーム と呼んでいます。
observable
プロパティは、[1,2,3,4,5]と言う 配列ではなく 、1,2,3,4,5という要素が流れる ストリーム なのです。
ついでにsubscribe(onNext:)
について。
先ほどは、
配列である
observable
にsubscribe
メソッドを呼ぶことで、配列の要素を順番に処理するための仕組みに変換することができます。
としていました。これはイメージ的には間違いではありませんが、先ほど、配列ではなくストリームとしましたので、
「ストリームに流れてきた要素一つ一つを順番に処理できるようにする」
とした方がより正しいでしょう。
ただ、subscribe
メソッドには引数として、onNext
という要素を使って任意の処理ができる、クロージャを指定する引数がありました。
なので、subscribe
メソッドは、
ストリームに流れてきた要素一つ一つを順番に処理できるようにして
流れてきた要素一つ一つに任意の処理を指定するメソッド
とした方がよりいいでしょう。
わかりにくい方のために、これも物流倉庫的に考えることにしましょう。
.subscribe(onNext:)
はコンベアのそばに常駐する作業員
.subscribe(onNext:)
は作業員のようなイメージです。
彼はコンベアのそばに常に居て、荷物をひとつひとつ持ち上げ、荷物を振り分ける仕事を任されています。
これを先ほどの、
ストリームに流れてきた要素一つ一つを順番に処理できるようにして
流れてきた要素一つ一つに任意の処理を指定するメソッド
に置き換えてみると、
にようになります。
let observable = Observable.of(荷物1,荷物2,荷物3,荷物4,荷物5)
let subscription = observable.subscribe(onNext: { 持ち上げた荷物 in
// 持ち上げた荷物を振り分ける処理を開始
if 持ち上げた荷物.行先 == "A店" {
// パレットAに荷物を下ろす処理
・
・
・
})
のような感じです。(少々乱雑ですが・・・。)
ただコンベアに荷物を流すだけでなく、コンベアのそばに作業員を常駐させ、彼に任意の処理をさせることで、ひとつ一つの要素に処理を施すことができるのです。
ここから本題に戻ります。
物流倉庫には、トラックが運んできた荷物を第三者がコンベア・ラインに流し、作業員が、コンベア・ラインのそばに立って、それぞれの配送先へ仕分け作業をしていきます。(そういう倉庫だとしましょう。)
もちろん大きな倉庫では、荷物が膨大で絶え間なくコンベアに荷物が流れていると思いますが、時期・時間によってはそうではないかもしれません。つまり、コンベアに荷物が流れていない状態もありうるわけです。
これを同期処理に強い、sequence
で書くことはできません。
プル型のsequence
はコンベアに流れてくる荷物を処理するというよりかは、ピッキング作業員がリストを持って倉庫内の荷物棚から、荷物を 自ら取りに行く ようなイメージです。
array.makeIterator()
というのは、
- 仕事の全数を確定させ、リスト化し
- そのリストを持ったピッキング作業員を派遣する
- 終われば終了
に近いです。つまりこれは同期処理です。
sequenceで書く場合、処理する要素の数がすでに確定していて、それが終われば処理が終了するので、非同期で書くことが難しくなります。
そこで、Observable
が登場します。彼はピッキング作業員とは違い、自ら取りに行くのではなく、流れてきた荷物を仕分け します。
作業員は、コンベアのそばに常駐しています。もしラインに荷物がない場合、第三者が流してくれるまで待機しています。
荷物が流されたタイミングで、待機を終了し、また荷物の仕分けを行うのです。これは非同期処理です。
Observable
は非同期的な処理に適しているのです。
監視 という言葉
subscribe
メソッドの呼び出しで監視の開始という解説をよく目にしますが、コンベアから流れてくる荷物を監視しているという感じです。
実際のコード
実際、Observable.of()
というコードはあまり見かけない気がします。
というのも、Observable.of(1,2,3,4,5)
のように、ストリームに流れるデータがあらかじめ決まっているという状態はあまりないからです。むしろ決まっている場合は、同期処理で書くべきでしょう。
では、Observableが力を発揮するのはどういう場面なのか。
一例として挙げられるのが、UI部品のイベント監視の際です。
button.rx.tap
.subscribe(onNext: {
print("タップされました")
})
}
ボタンのタップはユーザが行うものです。タイミングが決まっているわけではありません。
そのため同期処理で書くことができないのです。
荷物が流れてくるのを待っている作業員と同様に、UI
(この場合 UIButton
)をタップするのを待っているのがObservable
なのです。
button.rx.tap
がストリームに該当します。タップイベントが流れる予定のコンベアが、button.rx.tap
です。
この button
は view
ファイルなどで生成されたUIButton
ですが、button.rx.tap
と一行を書くだけで、button
のタップイベントをストリームに流すことができます。
上記コードをイラストで書くとこうなります。
おわりに:Observableとは「流れる荷物」、subscribeとは「構えて待つ作業員」
概念 | イメージ | 説明 |
---|---|---|
Observable |
コンベアライン | 値が流れてくる「道筋」。自ら値は出さない。 |
.of(1,2,3) |
荷物を流す仕組み | ストリームの定義。subscribe で動き出す。 |
subscribe() |
作業員を配置して構える | 値が流れてきた時の処理方法をセットする。 |
onNext: |
荷物を取って仕分ける動作 | 流れてきた値に対する処理(クロージャ) |
Sequence |
倉庫から自分で取りに行く仕事 | 同期的。必要な分を自分で引っ張る(Pull型) |
Observable |
勝手に流れてくるから構えて待つ | 非同期。流れてくるものを受け取る(Push型) |
今回の「物流倉庫+コンベア+作業員」の例が、あなたの中で「なるほど!そういうことか!」に繋がっていたら嬉しいです。
次は bind
や disposeBag
、Subject
との違いも、同じように可視化してまとめられたらいいなと思っています。