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 件のコメント:
コメントを投稿
注: コメントを投稿できるのは、このブログのメンバーだけです。