Nobollel開発者ブログ

Nobollelのエンジニアが、UnityやCocos2d-xの旬な情報・技術を紹介します。

【Reactive Extensions】 IObservableの合成と分岐入門その5

【Reactive Extensions】 IObservableの合成と分岐入門その5

おはこんばんにちは、tsuchimotoです。

今回は前回の第4回に引き続き、UniRxを使ったIObservableの合成と分岐入門の5回目になります。

前回までのリンク

Ambメソッド

Ambメソッドについて説明します。

Ambメソッドは複数のIObservableのシーケンスの中から一番最初に値を発行したIObservableのシーケンスの値を後続に流すメソッドです。

このメソッドのオーバーロードを以下に示します。

// 引数で渡した全てのIObservable<T>から最初に値を発行したIObservable<T>の値を後ろに流す
public static IObservable<T> Amb<T>(params IObservable<T>[] sources);
// sources内の全てのIObservable<T>から最初に値を発行したIObservable<T>の値を後ろに流す
public static IObservable<T> Amb<T>(this IEnumerable<IObservable<T>> sources);
// firstとsecondのうち最初に値を発行したものの値を後ろに流す
public static IObservable<T> Amb<T>(this IObservable<T> first, IObservable<T> second);

他の合成系メソッドと同様に可変長引数やIEnumerable>の拡張メソッドや2つの合成に特化したオーバーロードが用意されています。

Ambメソッドのコード例を以下に示します。

Observable.Amb(
    // 3秒後に値を発行するIO<T>
    Observable.Timer(TimeSpan.FromSeconds(3)).Select(_ => "3sec"),
    // 10秒後に値を発行するIO<T>
    Observable.Timer(TimeSpan.FromSeconds(10)).Select(_ => "10sec"),
    // 2秒後に値を発行するIO<T>
    Observable.Timer(TimeSpan.FromSeconds(2)).Select(_ => "2sec"),
    // 30秒後に値を発行するIO<T>
    Observable.Timer(TimeSpan.FromSeconds(22)).Select(_ => "30sec"),
    // 5秒後に値を発行するIO<T>
    Observable.Timer(TimeSpan.FromSeconds(6)).Select(_ => "5sec"))
    .Subscribe(
        s => Debug.LogFormat("OnNext: {0}", s),
        () => Debug.Log("OnCompleted"));

Timerメソッドを使って、指定した時間が経過した後に、値を発行するIObservableのシーケンスをAmbメソッドで合成しています。

実行結果を以下に示します。

OnNext: 2sec
OnCompleted

引数で渡した中で一番早く値を発行する Observable.Timer(TimeSpan.FromSecond(2)).Select(_ => "2sec")の結果が OnNextやOnCompletedに渡っていることが確認できます。

Switchメソッド

Switchメソッドについて説明します。

Switchメソッドは複数のIObservableのシーケンスの中から値が発行される度に、一つ前のシーケンスの値を流すのを辞めて、 後から発行されるシーケンスの値を流し始めます。

要するに、後から発行されたIObservableシーケンスにどんどん切り替えていくのです。

メソッドの定義は以下のようになります。

public static IObservable<T> Switch<T>(this IObservable<IObservable<T>> sources);

Switchメソッドのコード例を以下に示します。

// 1から4の値を発行するシーケンス
IObservable<string> source = Observable.Range(1, 4)
    // 1から4の値をそれぞれ1ミリ秒毎に文字列を発行する4つのシーケンスに変換
    .Select(i => 
        Observable.Interval(TimeSpan.FromMilliseconds(i)).Take(3).Select(sec =>
            string.Format("i:{0} {1}MSec", i, sec)
        )
    )
    // Switchメソッドで合成
    .Switch();

source.Subscribe(
    s => Debug.LogFormat("OnNext: {0}", s),
    () => Debug.Log("OnCompleted"));

実行結果は以下になります。

OnNext: i:4 0MSec
OnNext: i:4 1MSec
OnNext: i:4 2MSec
OnCompleted

ここで比較のためにSwitchをMergeに変えて実行してみましょう。

コードは以下になります。

// 1から4の値を発行するシーケンス
IObservable<string> source = Observable.Range(1, 4)
    // 1から4の値をそれぞれ1ミリ秒毎に文字列を発行する4つのシーケンスに変換
    .Select(i => 
        Observable.Interval(TimeSpan.FromMilliseconds(i)).Take(3).Select(sec =>
            string.Format("i:{0} {1}MSec", i, sec)
        )
    )
    // Mergeメソッドで合成
    .Merge();

実行結果は以下になります。

OnNext: i:1 0MSec
OnNext: i:2 0MSec
OnNext: i:3 0MSec
OnNext: i:4 0MSec
OnNext: i:1 1MSec
OnNext: i:2 1MSec
OnNext: i:3 1MSec
OnNext: i:4 1MSec
OnNext: i:1 2MSec
OnNext: i:2 2MSec
OnNext: i:3 2MSec
OnNext: i:4 2MSec
OnCompleted

Mergeメソッドでは4つのIObservableシーケンスから発行された値はすべて流されました。 対して、Switchメソッドでは4番目のシーケンスから発行された値しか流れていません。

Switchメソッドでは新たなシーケンスにどんどん切り替わって行き、最後の4番目のシーケンスの値しか流れて来ないことが分かります。