IIJmioのiOSサンプルアプリケーションにおいて、ReactiveCocoaを用いているネットワークまわりの処理を簡単に紹介します。
やりたいこと / やっていること
- IIJmioの情報を取得するには次の2つのエンドポイントにアクセスして、それぞれのJSONを取得する必要があります。
- https://api.iijmio.jp/mobile/d/v1/coupon/
 - https://api.iijmio.jp/mobile/d/v1/log/packet/
 
 - さらに、この2つから得られるJSONツリーの構造は、トップレベルが同じようになっているので、後の処理を考えると2つのデータをマージしたほうが楽になります。
 - 当然、エンドポイントにアクセスするにはOAuth2による認証を行って、アクセストークンを得ている必要があります。
 - アクセストークンが無効なら、再取得する必要があります。
 
今回説明するのは、1と4あたりになります。2と3にも多少は触れますが、詳細は説明しません。
AFNetworking-RACExtensionsとdefer:
まず、IIJmioのクーポンAPIからJSONを取得するシグナルを作成するMIOServiceViewModelのloadInformationSignalは次のようになっています。
- (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];
}
getCouponとgetPacketはAFNetworking-RACExtensionsの
rac_startJSONRequestOperationWithRequest:を使って、IIJmioの情報取得のための2つのエンドポイントにアクセスし、シグナルとしてJSONを変換したNSDictionaryを返します。
ただし、rac_startJSONRequestOperationWithRequest:がホットなシグナルなので、シグナル作成時に通信を開始してしまいます
(その逆のコールドなシグナルはサブスクライブするまで実行されません)。
それを防ぐためにdefer:を用いて、ホットシグナルをコールドシグナルに変換しています。
シグナルのフロー
前に紹介した通り、ReactiveCocoaはフローベースなフレームワークなので、図で説明していきましょう。
self.restHelper.accessTokenがnilでない場合の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 件のコメント:
コメントを投稿
注: コメントを投稿できるのは、このブログのメンバーだけです。