RxSwiftで2つのobserveをまとめて、処理を1回しか流れないようにする


ちょっとタイトルだけだと何を言っているかわからないかもしれない

2つのデータの更新を購読していた時に困った

komatta.swift
UserDefaults.standard.rx.observe(String.self, "foo")
    .subscribe(
        onNext: {
            // イケてるかもしれない処理を呼び出す
        }
    )

UserDefaults.standard.rx.observe(String.self, "bar")
    .subscribe(
        onNext: {
            // イケてるかもしれない処理を呼び出す
        }
    )

とまぁ、こんな感じです。
そして、この上の2つの関数はそれぞれ同じ関数……しかも外部APIを叩く系の処理を呼び出しているんですね。
普段であれば問題なかったんですが……

そして事件は起きた

jiken.swift
UserDefaults.standard.archive(key: "foo", value: "foo_value")
UserDefaults.standard.archive(key: "bar", value: "bar_value")

そう、fooとbarを関数内で同時に更新する処理が現れてしまったのです……
subscribeしていたのもあって、2つそれぞれ実行されてしまうことに

ambだとダメだったよ……

排他制御を考えて実装する等、アプローチは色々とあることにはあります。
ただ、そこはさすがのRxSwiftでした。ちゃんと問題を解決する機能が用意されています

kawano_nagare_wo_hitotu_ni.swift
let foo = UserDefaults.standard.rx.observe(String.self, "foo")
let bar = UserDefaults.standard.rx.observe(String.self, "bar")

foo.amb(bar).subscribe(
    onNext: {
        // イケてるかもしれない処理を呼び出す
    }
)

ambについてはここで!

と、ウキウキルンルン気分で実装していたんですがダメでした

bakuhatsu.swift
// fooの方が先なので、fooの購読のみ有効になる
let foo = UserDefaults.standard.rx.observe(String.self, "foo")
let bar = UserDefaults.standard.rx.observe(String.self, "bar")


// ...略

// barだけで更新すると、fooの購読だけが有効になっているので動かない!
let bar = UserDefaults.standard.rx.observe(String.self, "bar")

今度こそ1回しか実行されないようにする

@kakajika さんに指摘していただいて答えが見つかりました。
コメント本当にありがとうございます

kondo_koso_kawano_nagare_wo_hitotu_ni.swift
let foo = UserDefaults.standard.rx.observe(String.self, "foo")
let bar = UserDefaults.standard.rx.observe(String.self, "bar")

Observable.of(firstObserve, secondObserve).merge()
    .flatMapFirst {
        // 非同期処理を返却
    }
    .subscribe(
        onNext: { result in
            // 非同期処理の結果を元に、イケてるかもしれない処理を呼び出す
        }
    )

まずmergeで、fooとbarのobserveを1つにしてみました。
このままだと、更新される度に2回呼ばれることは変わりないので、
flatMapFirstを入れて非同期処理が実行中であれば後続の処理を呼び出さない! という風にしています。

debounceやthrottleでもいいかな?と思ったのですが、時間の設定が必要であり、変更の反映までほんの僅かですが時間がかかりそうだったので、このパターンではflatMapFirstだけにしています。

これで正常に動作するかどうかは……要テストなので、テストしてきます