2014/01/09

IIJmioのiOSサンプルアプリのネットワーク周りのシグナル処理について

IIJmioのiOSサンプルアプリケーションにおいて、ReactiveCocoaを用いているネットワークまわりの処理を簡単に紹介します。

やりたいこと / やっていること

  1. IIJmioの情報を取得するには次の2つのエンドポイントにアクセスして、それぞれのJSONを取得する必要があります。
    • https://api.iijmio.jp/mobile/d/v1/coupon/
    • https://api.iijmio.jp/mobile/d/v1/log/packet/
  2. さらに、この2つから得られるJSONツリーの構造は、トップレベルが同じようになっているので、後の処理を考えると2つのデータをマージしたほうが楽になります。
  3. 当然、エンドポイントにアクセスするにはOAuth2による認証を行って、アクセストークンを得ている必要があります。
  4. アクセストークンが無効なら、再取得する必要があります。

今回説明するのは、1と4あたりになります。2と3にも多少は触れますが、詳細は説明しません。

AFNetworking-RACExtensionsとdefer:

まず、IIJmioのクーポンAPIからJSONを取得するシグナルを作成するMIOServiceViewModelloadInformationSignalは次のようになっています。

- (RACSignal*)loadInformationSignal {
    RACSignal* coupon = [RACSignal defer:^RACSignal* { return [self.restHelper getCoupon]; }];
    RACSignal* packet = [RACSignal defer:^RACSignal* { return [self.restHelper getPacket]; }];

    RACSignal* sig = [[coupon concat:packet] collect];
    return self.restHelper.accessToken? sig : [[[self.restHelper authorize] catch:self.errorBlock] concat:sig];
}

getCoupongetPacketAFNetworking-RACExtensionsrac_startJSONRequestOperationWithRequest:を使って、IIJmioの情報取得のための2つのエンドポイントにアクセスし、シグナルとしてJSONを変換したNSDictionaryを返します。

ただし、rac_startJSONRequestOperationWithRequest:がホットなシグナルなので、シグナル作成時に通信を開始してしまいます (その逆のコールドなシグナルはサブスクライブするまで実行されません)。

それを防ぐためにdefer:を用いて、ホットシグナルをコールドシグナルに変換しています。

シグナルのフロー

前に紹介した通り、ReactiveCocoaはフローベースなフレームワークなので、図で説明していきましょう。

self.restHelper.accessTokennilでない場合のloadInformationSignalのフローは次のようになります。

これを受けているloadInformationをとりあえず示すと、次のようになっています。

ここでは処理が分かれています。赤の矢印がerrorイベントを受けた場合で、青の矢印がそれ以外のイベントを受けた場合です (灰色の矢印はどの場合でも)。 なお、イベントの3つの型については以前の記事「Safx: ReactiveCocoaのシグナルとサブスクライブについて」などを参照してください。

concat:は2つのシグナルを繋げるだけですが、collectはこの繋げたシグナルのcompletedイベントを待ってから、一連の値を配列にまとめてnextイベントとして送ります (反対に、errorイベントが来る場合はそれをそのまま送信します)。

今回の例において、errorイベントが発生する原因には、通信エラーなども考えられますが、OAuth2のアクセストークンが無効なことも考えられます。

というわけで、それを考慮したフローを次に示します。

複雑そうに見えますが、実はOAuthのアクセストークンをnilにしてから再度loadInformationSignalを呼んでいるだけです (だから上と右がだいたい同じ)。 loadInformationSignalはアクセストークンがnilならauthorizeしますので上のフローのようになるわけです。

失敗してもトークン取得に成功したらもう一度データ取得を行って、正常系処理のmap:に戻っているのがわかります。

で、上記フローを煎じ詰めたloadInformationの中身が次のコードになります (エラー処理は若干はしょっています)。

[[[self loadInformationSignal] catch:^RACSignal *(NSError *error) {
    if (OAuthTokenFailure) { // OAuth2のアクセストークンが無効
        self.restHelper.accessToken = nil;
        return [self loadInformationSignal];
    }
    return [RACSignal empty]; // それ以外のエラーなのであきらめる
}] map:^(NSArray* array) {
    // データをまとめる        
}];

catchがどのような働きをするのかは以前の記事「Safx: ReactiveCocoa 3.0のconcat:とcatch:について」を参照ください。

関連リンク

0 件のコメント:

コメントを投稿

注: コメントを投稿できるのは、このブログのメンバーだけです。