はじめに
備忘録としてQiitaに残します。
Cold・Hotを理解する前に
ColdもHotもObservableの性質のことを指します。
・SubjectはHotである
Observableの派生であるSubjectは全てHotなObservableに該当します。
・OperatorはColdである
各種Operatorは内部でObservableを返しますがColdなObservableを返します。
※もちろん、例外もあります。
ColdなObservableの性質
①subscribeされるまで動作しない
ColdなObservableはsubscribeしないと動作しない性質を持っています。
つまり、値を流しません(ストリームが稼働していない状態)。
// sequence(Cold) → map(Cold)
Observable.of("event")
.map { text -> String in
print(text)
return text
}
// 何も出力されない
subscribeすることによって値が流れます。
Observable.of("event")
.map { text -> String in
print(text)
return text
}
.subscribe()
.disposed(by: disposeBag)
// event
②分岐ができない
これは説明するより、以下のコードで確認した方が分かりやすいです。
// textField(Hot) -> map(Cold)
let observable = textField.rx.text.filterNil().map { text -> String in
print("\(text.count)")
return String(describing: text.count)
}
observable.bind(to: label1.rx.text).disposed(by: disposeBag)
observable.bind(to: label2.rx.text).disposed(by: disposeBag)
上記のコードではfilterNil( )でnilを除外し、TextFieldに入力された値をmapで加工して2つのLabelに反映しています。
出力結果はどうなるでしょうか?
0
0
1
1
2
2
文字を入力する度に、map内の処理が2回呼ばれました。
ColdなObservableでは、2つのオブジェクトに対してsubscribeすると
別々の2本のストリームが流れます。
ColdなObservableで複数subscribeした場合、それぞれに対してObservableが複製されてしまうんですね。
1.Hot -> Cold -> Observer
// 複製されてしまう!
2.Hot -> Cold -> Observer
HotなObservableの性質
①subscribeされなくても動作する
HotなObservableではsubscribeしなくても値が流れます。
例えば以下を実行したとします。
var behaviorSubject = BehaviorSubject<String>(value: "value")
// subject(Hot) -> map(Cold)
behaviorSubject.map { text -> String in
print(text)
return text
}
// 何も出力されない
ColdなObservableの性質で述べたように何も起きません。
subscribeされるまで動作しないからですね。
ではHotなObservableに変換してみましょう。
冒頭でも述べたように各種OperatorはColdなObservableを返します。
ですがHot変換オペレータと呼ばれるOperatorを使用すればHotなObservableに変換することが出来ます。
今回は、その中の1つであるPublish()を使ってみたいと思います。
var behaviorSubject = BehaviorSubject<String>(value: "value")
// subject(Hot) -> map(Cold) -> publish(Hot)
behaviorSubject.map { text -> String in
print(text)
return text
}
.publish() // Hot変換する
.connect() // 値が流れる
.disposed(by: disposeBag)
// value
subscribeしていないのに出力されましたね。
これはPublish( )内部でObservableをConnectableObservableに変換して返しているからなんですね(違う記事で詳しくまとめる予定)。
通常のObservableではsubscribeして値を流しますが、ConnectableObservableではconnect()が呼ばれて値を流し始めます。
つまり、connect( )を呼びだせばsubscribeしなくても値を流すことができるという訳です。
②分岐できる
では、先ほどのコードをHot変換してみましょう。
// textField(Hot) -> map(Cold) -> publish(Hot)
let observable = textField.rx.text.filterNil().map { text -> String in
print("\(text.count)")
return String(describing: text.count)
}
.publish()
observable.bind(to: label1.rx.text).disposed(by: disposeBag)
observable.bind(to: label2.rx.text).disposed(by: disposeBag)
observable.connect().disposed(by: disposeBag)
結果として以下のようになりました。
0
1
2
文字を入力する度に、map内の処理が1回しか呼ばれていない事がわかりますね。
複数subscribeしているのにも関わらず、同じ値がそれぞれに流れているからこそ、このような挙動になります。
1. Hot -> Cold -> Hot -> Observer
| // 分岐するから複製されない!
-> Observer
因みに分岐後の値は別の値として扱われるので下記のように個別に処理を加えることが出来ます。
observable.bind(to: label1.rx.text).disposed(by: disposeBag)
observable.map { text -> String in
"*\(text)*"
}
.bind(to: label2.rx.text)
.disposed(by: disposeBag)
おわりに
RxSwiftは難しいということが理解できました。
正直合っているか分からないので間違っている部分や疑問に思うところがあれば
コメントして下さると有り難いです。
次回はもっとHotなObservableについて理解を深めたいと思います。
参考資料
今日こそ理解するHot / Cold
RxSwift + RxCocoaに関する基礎知識
RxSwiftについてようやく理解できてきたのでまとめることにした(4)
RxJS を学ぼう #4 - COLD と HOT について学ぶ / ConnectableObservable