RxSwift combineLatest で いずれかの更新を無視する


追記

withLatestFromを使えば良い!って知りませんでした。たはー
ということで、下記はあんまり読まないで良い内容です。。

前書きのようなもの

RxSwiftのcombineLatestは、複数のストリームを比較するために利用します。挙動としては、比較対象のいずれかの値の更新が行われた時にストリームが流れてきます。例えば下記のような場合において

let main = PublishSubject<Int>()
let sub = PublishSubject<Int>()

Observable
.combineLatest(main, sub)
.bind { (mainValue, subValue) in
     NSLog("main, sub > \(mainValue)-\(subValue)")
}

sub.onNext(1) // main側の値がないので何も出力されない
main.onNext(2) // 2-1
sub.onNext(2) //  2-2
sub.onNext(3) //  2-3
main.onNext(10) // 10-3
main, sub > 2-1
main, sub > 2-2
main, sub > 2-3
main, sub > 10-3

main側、sub側 どちらの値の更新も契機となって、イベントストリームが流れてきます

やりたいこと

この例だと、sub側の更新ではストリームを流すことはしないで、main側が更新されたときに、一緒に最新のsubの値が欲しいのです。
つまり、期待する出力は下記のようになります

main, sub > 2-1
main, sub > 10-3

オススメしない実装

これを上手に言語化できないのが、自分の技術力の無さなのですが、、
rxのオペレータ (map、filter、bind、etc...) にはクロージャを渡しますが、そのクロージャに渡ってくる引数以外の値を利用することはオススメしません。
マルチスレッドを考えた時に、期待した値になっているか保証がないとの、そもそもクロージャにおいて参照透明性がありません。

let main = PublishSubject<Int>()
let sub:Int = 0

main.bind { (mainValue) in
     //self.sub はこのクロージャの世界の"外"の値であって、参照透明性を考慮してない
     NSLog("main, sub > \(mainValue)-\(self.sub)")
}

sub = 1 // 値を変えてから
main.onNext(2) // イベントを起こすが
sub = 2 //マルチスレッドだと、
sub = 3 //この値の変更が onNext(2)を追い越してしまうのか保証がない
main.onNext(10)

combineLatest + distinctUntilChanged

Rx の distinctUntilChanged は "新旧の値を比較して、更新を通知しない条件" を返す為に利用できます。

Observable
.combineLatest(main, sub)
.distinctUntilChanged { (old, new) -> Bool in
  //old は (main, sub)というタプル、newにも(main, sub)のタプルが入ってくる
  //新旧のmain側の値が同じ(=変化してない)場合には、イベントの通知を行わない
   return old.0 == new.0
}
.bind { (mainValue, subValue) in
   NSLog("main, sub > \(mainValue)-\(subValue)")
}
main, sub > 2-1
main, sub > 10-3

ほら、main 側の値が変わった時だけストリームが流れてきてますよね!