0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

RxSwiftが訳わからんすぎる。

Last updated at Posted at 2025-05-16

はじめに

  • Observable ってなんなん?
  • subscribe(onNext:) の「購読」ってどういう意味?
  • bindsubscribe?違いがようわからん…。

RxSwift の基本である「Observable」は、配列(Sequence)とは似て非なるもの。特に「非同期で値が流れてくる」「処理がイベントで発火する」といった特性にピンと来ていないと、コードだけ見ても理解が追いつきません。

この記事では、物流倉庫とコンベア作業 を例にして、Observable の仕組みと subscribe の意味を図解で徹底的に分かりやすく書いてみました。

全くの初心者なので、間違いがある場合があります。
初心者・MVVM学習中の私に、間違いがあればご指摘ください。

Observableってなんぞや

Observableはシーケンス操作のためのクラスです。
シーケンス操作というと Arrayfor-in の組み合わせを連想する人が多いのではないでしょうか。swiftの標準ライブラリには、Sequenceというプロトコルがあります。

実は、RxSwiftObservableUIKitsequence は、値の処理方法が違えど設計思想は同じなのです。

let array = [1,2,3,4,5]
for value in array{
    print(value)
}

これは標準ライブラリの Sequence プロトコルを用いて、配列を用意し、for-inを使って値を一つずつ取り出してprint処理しています。

この処理は、RxSwiftObservableを使って同じ処理をすることができます。

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ですが、実はObservablesubscribeも同じ役割をしています。

let observable = Observable.of(1,2,3,4,5)

まず、observableは、現時点では let array = [1,2,3,4,5]と同じと考えていただいて差し支えありません。(ストリームであるという違いがありますが、今は配列と考えてください。違いは必ず後述します。)

let subscription = observable.subscribe()

配列であるobservablesubscribeメソッドを呼ぶことで、配列の要素(厳密には違う)を順番に処理するための仕組みに変換することができます。
つまり、SequencemakeIterator()Observablesubscribe()は、両者とも配列の要素を順番に処理するための仕組みに変換しているという点で同じなのです。

この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 が存在しているのか。
結論から言うと、「非同期」的な処理に向いているからです。

「非同期」とシーケンス

上記で、ObservableSequenceは同じであると書きましたが、設計思想が同じなだけで、値の処理方法全く違います。ごめんなさい。

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 : コンベア・ラインのようなものを用意する。

image.png

  • .of(1,2,3,4,5) : 1,2,3,4,5 という荷物をコンベア・ラインに流す。

image.png

つまり、observableプロパティは 1,2,3,4,5 という荷物が流れるコンベアそのもの です。
以下のようなイメージです。

image.png

このコンベア・ラインを、Rx的に言い換えると ストリーム と呼んでいます。

image.png

observableプロパティは、[1,2,3,4,5]と言う 配列ではなく 、1,2,3,4,5という要素が流れる ストリーム なのです。

image.png

ついでにsubscribe(onNext:)について。

先ほどは、

配列であるobservablesubscribeメソッドを呼ぶことで、配列の要素を順番に処理するための仕組みに変換することができます。

としていました。これはイメージ的には間違いではありませんが、先ほど、配列ではなくストリームとしましたので、

「ストリームに流れてきた要素一つ一つを順番に処理できるようにする」

とした方がより正しいでしょう。

ただ、subscribeメソッドには引数として、onNextという要素を使って任意の処理ができる、クロージャを指定する引数がありました。

なので、subscribeメソッドは、

ストリームに流れてきた要素一つ一つを順番に処理できるようにして

流れてきた要素一つ一つに任意の処理を指定するメソッド

とした方がよりいいでしょう。

わかりにくい方のために、これも物流倉庫的に考えることにしましょう。

.subscribe(onNext:) はコンベアのそばに常駐する作業員

image.png

.subscribe(onNext:) は作業員のようなイメージです。
彼はコンベアのそばに常に居て、荷物をひとつひとつ持ち上げ、荷物を振り分ける仕事を任されています。

image.png

これを先ほどの、

ストリームに流れてきた要素一つ一つを順番に処理できるようにして
流れてきた要素一つ一つに任意の処理を指定するメソッド

に置き換えてみると、

image.png

にようになります。

let observable = Observable.of(荷物1,荷物2,荷物3,荷物4,荷物5)
let subscription = observable.subscribe(onNext: { 持ち上げた荷物 in

    // 持ち上げた荷物を振り分ける処理を開始
    
    if 持ち上げた荷物.行先 == "A店" {
        // パレットAに荷物を下ろす処理
    
    
    
})

のような感じです。(少々乱雑ですが・・・。)

ただコンベアに荷物を流すだけでなく、コンベアのそばに作業員を常駐させ、彼に任意の処理をさせることで、ひとつ一つの要素に処理を施すことができるのです。

ここから本題に戻ります。

物流倉庫には、トラックが運んできた荷物を第三者がコンベア・ラインに流し、作業員が、コンベア・ラインのそばに立って、それぞれの配送先へ仕分け作業をしていきます。(そういう倉庫だとしましょう。)

もちろん大きな倉庫では、荷物が膨大で絶え間なくコンベアに荷物が流れていると思いますが、時期・時間によってはそうではないかもしれません。つまり、コンベアに荷物が流れていない状態もありうるわけです。

image.png

これを同期処理に強い、sequenceで書くことはできません。
プル型のsequenceはコンベアに流れてくる荷物を処理するというよりかは、ピッキング作業員がリストを持って倉庫内の荷物棚から、荷物を 自ら取りに行く ようなイメージです。

image.png

array.makeIterator()というのは、

  • 仕事の全数を確定させ、リスト化し
  • そのリストを持ったピッキング作業員を派遣する
  • 終われば終了

に近いです。つまりこれは同期処理です。
sequenceで書く場合、処理する要素の数がすでに確定していて、それが終われば処理が終了するので、非同期で書くことが難しくなります。

そこで、Observableが登場します。彼はピッキング作業員とは違い、自ら取りに行くのではなく、流れてきた荷物を仕分け します。
作業員は、コンベアのそばに常駐しています。もしラインに荷物がない場合、第三者が流してくれるまで待機しています。

image.png

荷物が流されたタイミングで、待機を終了し、また荷物の仕分けを行うのです。これは非同期処理です。
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です。
この buttonview ファイルなどで生成されたUIButtonですが、button.rx.tapと一行を書くだけで、buttonのタップイベントをストリームに流すことができます。

image.png

上記コードをイラストで書くとこうなります。

image.png

おわりに:Observableとは「流れる荷物」、subscribeとは「構えて待つ作業員」

概念 イメージ 説明
Observable コンベアライン 値が流れてくる「道筋」。自ら値は出さない。
.of(1,2,3) 荷物を流す仕組み ストリームの定義。subscribe で動き出す。
subscribe() 作業員を配置して構える 値が流れてきた時の処理方法をセットする。
onNext: 荷物を取って仕分ける動作 流れてきた値に対する処理(クロージャ)
Sequence 倉庫から自分で取りに行く仕事 同期的。必要な分を自分で引っ張る(Pull型)
Observable 勝手に流れてくるから構えて待つ 非同期。流れてくるものを受け取る(Push型)

今回の「物流倉庫+コンベア+作業員」の例が、あなたの中で「なるほど!そういうことか!」に繋がっていたら嬉しいです。

次は binddisposeBagSubject との違いも、同じように可視化してまとめられたらいいなと思っています。


0
1
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
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?