【Unity】UniRx【3】 #86
前回の成果
IObserverインターフェースとストリームの寿命について学んだ。
今回やること
引き続き以下サイト様の手順に習って、UniRxを学んでいきます。
事前準備
アセットストアからUniRxをインポートします。
ストリームのソース
UniRxのストリームは以下の3つによって構成されています。
- メッセージを発行するソース
- メッセージを伝えるオペレータ
- メッセージを購読する
構成例
// メッセージを発行するソース Subject<string> subject = new Subject<string>(); subject // メッセージを伝えるオペレータ .Select(str => int.Parse(str)) // メッセージを購読する .Subscribe(x => { });
今回は、「メッセージを発行するソース」に焦点を当てて説明していきます。
メッセージを発行するソース
ストリームソースを用意する手段はいくつか存在します。
まずは、今まで使用したSubjectシリーズについて説明します。
Subjectシリーズ
今まで使用してきたSubjectですが、こちらを使用するのが基本となります。
このSubjectにはいくつか派生が存在するので、今回は以下の4つ紹介していきます。
まずは簡単に表で説明します。
名前 | 機能 |
---|---|
Subject<T> | OnNextで発行 |
BehaviorSubject<T> | OnNextで発行された最新の値をキャッシュ Subscribeでキャッシュされた値を発行 |
ReplaySubject<T> | OnNextで発行された値を全てキャッシュ Subscribeでキャッシュされた値を全て発行 |
AsyncSubject<T> | OnNextで発行された最新の値をキャッシュ OnCompletedでキャッシュされた値を発行 |
次に個別に解説していきます。
Subject<T>
OnNextで発行する基本的なものとなります。
Subject
/// <summary> /// Subject /// OnNextで発行 /// </summary> private void ExcuteSubject() { // イベント発行 Subject<int> subject = new Subject<int>(); // イベント登録 subject.Subscribe(x => { Debug.Log("OnNext : " + x); }, () => { Debug.Log("OnCompleted"); }); // イベント実行 subject.OnNext(1); subject.OnNext(10); subject.OnCompleted(); }
結果
OnNextが呼ばれると値を発行します。
BehaviorSubject<T>
OnNextで値を発行し、その値をキャッシュします。
次のOnNextでも同様に、値を発行しますが既にキャッシュされている値は破棄して、最新のもののみをキャッシュします。
Subscribeで新たに購読者を登録した場合、キャッシュした値を発行します。
このメソッドのみ、コンストラクタで初期値を指定する必要があります。
BehaviorSubject
/// <summary> /// BehaviorSubject /// OnNextで発行された最新の値をキャッシュ /// Subscribeでキャッシュされた値を発行 /// </summary> private void ExcuteBehaviorSubject() { BehaviorSubject<int> subject = new BehaviorSubject<int>(0); IDisposable stream1 = subject.Subscribe(x => { Debug.Log("ストリーム1 : " + x); }, () => { Debug.Log("ストリーム1 OnCompleted"); }); subject.OnNext(1); subject.OnNext(10); // 最新の値のみキャッシュされているので、OnNext(10)のみ呼ばれる IDisposable stream2 = subject.Subscribe(x => { Debug.Log("ストリーム2 : " + x); }, () => { Debug.Log("ストリーム2 OnCompleted"); }); subject.OnCompleted(); // OnCompleted後なので呼ばれない subject.OnNext(100); // 最新の値のみキャッシュされているので、OnCompletedのみ呼ばれる IDisposable stream3 = subject.Subscribe(x => { Debug.Log("ストリーム3 : " + x); }, () => { Debug.Log("ストリーム3 OnCompleted"); }); }
結果
ストリーム1を登録した際に、コンストラクタで指定されている0が発行されます。
ストリーム2を登録した際には、事前に呼ばれた10が発行されます。
ストリーム3を登録した場合、最後のキャッシュがOnCompletedなので、OnCompletedが発行されます。
ReplaySubject<T>
OnNextで値を発行し、その値をキャッシュします。
Subscribeで新たに購読者を登録した場合、すべての値を発行します。
ReplaySubject
/// <summary> /// ReplaySubject /// OnNextで発行された値を全てキャッシュ /// Subscribeでキャッシュされた値を全て発行 /// </summary> private void ExcuteReplaySubject() { ReplaySubject<int> subject = new ReplaySubject<int>(); subject.OnNext(1); IDisposable stream1 = subject.Subscribe(x => { Debug.Log("ストリーム1 : " + x); }, () => { Debug.Log("ストリーム1 OnCompleted"); }); subject.OnNext(10); // キャッシュされているので、OnNextも呼ばれる IDisposable stream2 = subject.Subscribe(x => { Debug.Log("ストリーム2 : " + x); }, () => { Debug.Log("ストリーム2 OnCompleted"); }); subject.OnCompleted(); // OnCompleted後なので呼ばれない subject.OnNext(100); // キャッシュされているので、OnNextも呼ばれる IDisposable stream3 = subject.Subscribe(x => { Debug.Log("ストリーム3 : " + x); }, () => { Debug.Log("ストリーム3 OnCompleted"); }); }
結果
ストリーム2を登録した際には、すでにキャッシュされている1と10が発行されます。
ストリーム3を登録した際に、OnCompletedが呼ばれていても全ての値が発行されます。
AsyncSubject<T>
OnNextで値を発行せずに最新の値のみキャッシュします。
そしてOnCompletedでキャッシュされた値を発行します。
Subscribeで新たに購読者を登録した場合、OnCompletedを呼ぶ前ですと何も発行されません。
OnCompleted後ですと、キャッシュされた値とOnCompletedが呼ばれます。
AsyncSubject
/// <summary> /// AsyncSubject /// OnNextで発行された最新の値をキャッシュ /// OnCompletedでキャッシュされた値を発行 /// </summary> private void ExcuteAsyncSubject() { AsyncSubject<int> subject = new AsyncSubject<int>(); subject.OnNext(1); // OnCompleted()が呼ばれていないので、OnNext(1)は呼ばれない IDisposable stream1 = subject.Subscribe(x => { Debug.Log("ストリーム1 : " + x); }, () => { Debug.Log("ストリーム1 OnCompleted"); }); subject.OnNext(10); IDisposable stream2 = subject.Subscribe(x => { Debug.Log("ストリーム2 : " + x); }, () => { Debug.Log("ストリーム2 OnCompleted"); }); // OnCompletedが呼ばれたので、最新のキャッシュも発行 subject.OnCompleted(); // OnCompleted後なので呼ばれない subject.OnNext(100); // OnNext(10)とOnCompletedを発行 IDisposable stream3 = subject.Subscribe(x => { Debug.Log("ストリーム3 : " + x); }, () => { Debug.Log("ストリーム3 OnCompleted"); }); }
結果
最新の値のみキャッシュされるので、全てのストリームで10のみ発行されます。
ストリーム3を登録した際に、OnCompletedが呼ばれていますが、キャッシュされた値とOnCompletedが呼ばれます。
Subjectシリーズ一覧
一覧としてまとめました。
名前 | OnNext時 | OnCompleted時 | Subscribe時 |
---|---|---|---|
Subject<T> | 値を発行 | OnCompletedを発行 | 何もしない |
BehaviorSubject<T> | 値を発行 最新の値のみキャッシュ |
OnCompletedを発行 | キャッシュされた値を発行 |
ReplaySubject<T> | 値を発行 値をキャッシュ |
OnCompletedを発行 | キャッシュされた全ての値を発行 |
AsyncSubject<T> | 最新の値のみキャッシュ | キャッシュされた値とOnCompletedを発行 | 何もしないが、OnCOmpletedが呼ばれた後だとキャッシュされた値とOnCompletedを発行 |
ReactivePropertyシリーズ
ReactiveProperty<T>
値を渡した際に、同じ値の場合何もしない、違う値の場合OnNextを発行してくれるものとなります。
ReactiveProperty<T>のサンプル
/// <summary> /// ReactivePropertyの実行 /// </summary> private void ExcuteReactiveProperty() { // ReactiveProperty ReactiveProperty<int> intRp = new ReactiveProperty<int>(); // 初期値を指定可能 ReactiveProperty<string> stringRp = new ReactiveProperty<string>("Hoge"); // Subscribe時に値を発行 intRp.Subscribe(x => { Debug.Log("int RP : " + x); }); stringRp.Subscribe(x => { Debug.Log("string RP : " + x); }); // 値を書き換えるとOnNext intRp.Value = 10; stringRp.Value = "Fuga"; // 同じ値なので何もしない intRp.Value = 10; stringRp.Value = "Fuga"; // 値の代入も可能 int value = intRp.Value; Debug.Log("value : " + value); }
結果
値が変更された時にOnNextが発行されています。
同じ値の場合には、何も発行されていません。
ReactiveCollection<T>
ReactivePropertyのListのようなものになります。
用意されているメソッドは以下となります。
- 要素の追加
- 要素の削除
- 要素数の変換
- 要素の上書き
- 要素の移動
- リストのクリア
ReactiveCollection<T>のサンプル
/// <summary> /// ReactiveCollectionの実行 /// </summary> private void ExcuteReactiveCollection() { // ReactiveCollection ReactiveCollection<string> rcString = new ReactiveCollection<string>(); // Subscribe時に値を発行 // 要素追加時 rcString .ObserveAdd() .Subscribe(x => { Debug.Log(string.Format("[{0}]番目に[{1}]を追加", x.Index, x.Value)); }); // 要素削除時 rcString .ObserveRemove() .Subscribe(x => { Debug.Log(string.Format("[{0}]番目の[{1}]を削除", x.Index, x.Value)); }); // 要素を追加するとOnNext rcString.Add("Hoge"); rcString.Add("Fuga"); rcString.Add("Foo"); // 要素を削除するとOnNext rcString.Remove("Fuga"); foreach (string str in rcString) { Debug.Log("rcStringの中身 : " + str); } // 存在しないので呼ばれない rcString.Remove("nothing"); }
結果
要素を追加、削除した時にOnNextが発行されています。
foreachで要素を確認した際にFugaがListから削除されているのがわかります。
ReactiveDictionary<T>
ReactivePropertyのDictionaryのようなものとなります。
基本的な挙動は、ReactiveCollection<T>と同じとなります。
ReactiveDictionary<T>のサンプル
/// <summary> /// ReactiveDictionaryの実行 /// </summary> private void ExcuteReactiveDictionary() { // ReactiveDictionary ReactiveDictionary<string, int> reactiveDictionary = new ReactiveDictionary<string, int>(); // Subscribe時に値を発行 // 要素追加時 reactiveDictionary .ObserveAdd() .Subscribe(x => { Debug.Log(string.Format("要素追加, Key : {0}, Value : {1}", x.Key, x.Value)); }); // 要素削除時 reactiveDictionary .ObserveRemove() .Subscribe(x => { Debug.Log(string.Format("要素削除, Key : {0}, Value : {1}", x.Key, x.Value)); }); // 要素数変化時 reactiveDictionary .ObserveCountChanged() .Subscribe(x => { Debug.Log(string.Format("要素数変化, Count : {0}", x)); }); // 要素を追加するとOnNext reactiveDictionary.Add("スライム", 10); reactiveDictionary.Add("ドラゴン", 100); reactiveDictionary.Add("魔王", 1000); // 要素を削除するとOnNext reactiveDictionary.Remove("スライム"); foreach(var keyValue in reactiveDictionary) { Debug.Log(string.Format("Dictionaryの中身, Key : {0}, Value : {1}", keyValue.Key, keyValue.Value)); } }
結果
要素を追加、削除した時にOnNextが発行されています。
また、ObserveCountChangedを追加しているので要素数が変化する度にOnNextが発行されます。
今回は以上となります。
ここまでご視聴ありがとうございました。