はじめに
ニフティクラウド mobile backend(以降NCMB)とは、ニフティさんが提供するmBaaSサービスです。
クラウドサーバ上に会員登録やデータの保存や検索ができるサービスです。
NCMB
Unity Plugin
このNCMBはUnity向けのPlguinを提供しており、無料で利用することができるようになっています。
https://github.com/NIFTYCloud-mbaas/ncmb_unity
NCMB Unity Pluginの問題点
さて、このUnity Pluginなのですが「APIの非同期実行時にコールバックがネストする」という問題点があります。
//ログインして成功したらユーザの詳細情報を取得する
NCMBUser.LogInAsync("test_user_id", "test_password", error =>
{
if (error != null)
{
//失敗
Debug.LogError(error);
}
{
//ログインに成功したらユーザの詳細取得
var currentUser = NCMBUser.CurrentUser;
currentUser.FetchAsync(exception =>
{
if (exception != null)
{
//失敗
Debug.LogError(exception);
}
else
{
//成功
//ユーザの登録されたメールアドレスの表示
Debug.Log(currentUser.Email);
}
});
}
});
解決してみた
こういう非同期処理のコールバックのネストを解消するデザインパターンとして古くからFuture/Promiseパターン
が利用されています。今回はこのデザインパターンと互換性のあるUniRxのIObservable
に置き換えることで、このコールバックのネスト問題を解消してみました。
NcmbAsObservable
というわけで、NCMB Unity PluginをUniRxで触れる用にするライブラリを作ってみました。
NcmbAsObservableからダウンロードしてください。
(いいライブラリ名が思いつかなかったので適当です)
使い方
using NcmbAsObservables;
をまず追加してください。それでいろいろ呼び出せるようになります。
例1: ログインしてデータ取得
NCMBUser
にstaticで生えているAPIメソッドはObservableFromNcmbUser
から呼び出すことができます。
ObservableFromNcmbUser
.LogInAsync("test_user_name", "hogehoge") //Login
.SelectMany(u => u.FetchAsyncAsStream()) //Fetch
.Subscribe(u => Debug.Log(u.Email), e => Debug.LogError(e));
このようにIObservable
化してしまえばコールバックのネストが消えてフラットにコードを書くことが出来るようになります。
例2:サインインしてユーザ情報を書き換える
NCMBが用意したオブジェクトに生えている~Async
系のAPIは末尾をAsyncAsStream
と呼び替えることでIObservable
版を呼び出すことができるようになります。
(本当は ~AsyncAsObservable
にしたかったんだけど、長さ1のストリームをObservableと呼ぶのに抵抗があったためこんな変な名前になりました。他に良い命名があればプルリクお願いします。)
var user = new NCMBUser();
user.UserName = "test_user_name";
user.Password = "hogehoge";
//サインイン→成功したらデータ書き換え→再取得→結果表示
//Signup
user.SignUpAsyncAsStream()
.SelectMany(u =>
{
//Change email and Age column when signed up
u.Email = "test@test.com";
u["Age"] = 20;
return u.SaveAsyncAsStream(); //Save
})
.SelectMany(u => u.FetchAsyncAsStream()) //Fetch
.Subscribe(u =>
{
Debug.Log(string.Format("{0}\t{1}\t{2}", u.UserName, u.Email, u["Age"]));
}, e =>
{
Debug.LogError(" Error:" + e);
});
このように、AsyncAsStream
に書き換えることでIObservable
化することができます。
また、途中で発生したエラーはOnError
を流れるのでこんな感じでエラーハンドリングもできます。
var user = new NCMBUser();
user.UserName = "test_user_name";
user.Password = "hogehoge";
//サインイン→成功したらデータ書き換え→再取得→結果表示
//Signup
user.SignUpAsyncAsStream()
.Catch((NCMBException e) =>
{
Debug.LogError("Error on sign up:" + e);
return Observable.Empty<NCMBUser>();
})
.SelectMany(u =>
{
//Change email and Age column when signed up
u.Email = "test@test.com";
u["Age"] = 20;
return u.SaveAsyncAsStream(); //Save
})
.Catch((NCMBException e) =>
{
Debug.LogError("Error on save:" + e);
return Observable.Empty<NCMBUser>();
})
.SelectMany(u => u.FetchAsyncAsStream()) //Fetch
.Catch((NCMBException e) =>
{
Debug.LogError("Error on Fetch:" + e);
return Observable.Empty<NCMBUser>();
})
.Subscribe(u =>
{
Debug.Log(string.Format("{0}\t{1}\t{2}", u.UserName, u.Email, u["Age"]));
}, e =>
{
Debug.LogError("Unknown Error:" + e);
});
例3: NCMBQueryを使う
NCMBQuery
はNCMBのデータストアに対してクエリを発行してオブジェクトを検索する時に使うオブジェクトです。
このNCMBQuery
は対応する型をジェネリックで渡さないといけない関係で拡張メソッド化ができませんでした。
なので代わりに NCMBQueryHelper<T>
を用意したのでこちらを経由してAPIを実行すればIObservable
化できるようになっています。
NCMBQuery
も同様に拡張メソッド経由でObservableとして扱えるようになっています。
var query = new NCMBQuery<NCMBObject>("Score");
query.OrderByDescending("score");
query.Limit = 5;
query
.FindAsyncAsStream()
.Subscribe(resultList =>
{
foreach (var o in resultList)
{
Debug.Log(o);
}
}, error => Debug.LogError(error));
最後に
触れる頻度の高いNCMBUser
を中心にIObservable
化しています。
まだコールバックをとる非同期APIのIObservable
化に漏れがあるかもしれませんので、見つけた時は報告いただけるかプルリクをくれるとありがたいです。