iOS Framework:MKNetworkKitの概要

本エントリはMugunth Kumar氏によるブログの投稿を翻訳したものである。

紹介の背景(訳者より)

ASIHTTPRequestはCocoa Touchで動作する素晴らしいネットワーク処理ラッパライブラリだった。しかし、ARC(Auto Reference Counting)に非対応である事と、今後も対応予定は無いとの噂もある事から、最近ではこれに代わる新しいライブラリの登場が期待されている。その候補の一つに、本エントリで紹介するMKNetworkKitが挙げられる。

僕が乗り換えを検討したのは、決定的な出来事が一つあったからだ。ARCで書いたiOSアプリが、Appleの審査員のiOS上でだけ上手くネットワーク通信出来ないという現象が発生したのだ。この現象の原因がASIHTTPRequestである裏付けとして、ASIHTTPRequestを使わずNSURLRequestで飛ばしたリクエストは正しくサーバに記録されていた。そこで、ARCに対応しておりメンテナンスも積極的にされていて、乗り換えやすそうなライブラリであるMKNetworkKitを導入してみた。選んだ理由は、このライブラリがCFNetworkではなく、NSURLConnection、NSURLRequestといったCocoa Touch標準のネットワーク処理クラス群を使って実装されている点である。導入の結果、無事Appleの審査員のiOS上で上手くネットワーク通信が出来て、審査を通る事が出来た。微々たる改善の余地は見られるものの、今のところ表立ったバグは見当たらない。

以上の経緯で、ぜひ他の方にもお薦めしたいと思って、本エントリを書く事にした。このような素晴らしいライブラリを作成したMugunth Kumar氏にこの場を借りてお礼を言いたい。ありがとう。

2013/06/04 修正: MKNetworkOperation:onCompletion:onErrorメソッドがdeprecatedのため、現行のMKNetworkOperation:addCompletionHandler:errorHandler:にサンプルコードを訂正しました。

目次

はじめに

ネットワーク・フレームワークが自動的にキャッシュしてくれたらどれだけ素敵だろうか?
クライアントがオフラインになっても、ネットワーク・フレームワークが自動的にネットワーク処理を覚えていてくれたらどれだけ素晴らしいだろうか?

ユーザはツイートとかフィードの既読化をオフラインの時にやるのを好む。
この時、何も特別な処理を書かなくても、ネットワーク・フレームワークがオンラインになった時にそれらの操作によるネットワーク処理してくれる。
MKNetworkKitを紹介しよう。

MKNetworkKitとは

MKNetworkKitはObjective-Cで書かれた、blocksベースでARC対応のネットワーク・フレームワークである。MKNetworkKitは二つの有名なネットワーク・フレームワークであるASIHTTPRequestとAFNetworkingにインスパイアされている。この二つのフレームワークの機能群を取り込みつつ、MKNetworkKitはいくつかの新しい機能を提供する。MKNetworkKitはコードを明快にするために、他のフレームワークよりもほんの少しだけ多くコードを書く必要がある。MKNetworkKitを使えば、ひどいネットワーク処理のコードを書くのは難しいだろう。

機能

超軽い

このフレームワークは主に2つのクラスといくつかのカテゴリメソッドで構成されている。つまり、MKNetworkKitはめちゃくちゃ簡単だって事だ。

アプリ全体に対して単一の共有キュー

アプリがインターネットの接続性に強く依存しているのなら、同時に実行するネットワーク通信処理の数を最適化するべきだ。残念ながら、この最適化を的確にやってくれるネットワーク・フレームワークは存在しない。ここで一つ例を挙げて、あなたがネットワーク通信の最適化をアプリ内でしなかった時にどんな問題が起こるのかを説明したい。

数枚の写真をサーバにアップロードする事を想定しよう(ColorとかBatchみたいな感じ)。ほとんどのモバイル・ネットワーク(3G)は、与えられたIPから3つ以上のHTTPによる同時接続は出来ない。つまり、デバイスからは、3Gでは複数同時にHTTP通信できない。Edgeはもっと悪い。ほとんどの場合、2つ以上のHTTPによる同時接続が出来ない。この制限はWi-Fiなどのブロードバンド環境ではかなり高い(6つ)。しかしながら、iOSでバイスはいつもWi-Fiに繋がっているとは限らないし、ほとんどの場合は3G回線だろう。この事は、アプリが2つしか同時に写真をアップロード出来ない事を意味している。今、スピードが遅い事が問題なのではない。本当の問題は、バックグラウンドで写真をアップロード中に別のビューを開いてサムネイル画像をロードしようとした時に起こる。もしちゃんとアプリ内でキューのサイズをコントロールしていなければ、サムネイル画像の読み込みはタイムアウトするだろう。これは本当によくないやり方だ。この場合の正しいやり方は、サムネイル画像の読み込みに優先度付けをするか、アップロードが終わるまで待つようにする事だ。これにはアプリ全体に対して一つのキューを持たせる事が必要だ。MKNetworkKitはそのインスタンスごとに一つの共有キューを使う事で、自動的にこの方法を実現する。MKNetworkKitはsingletonではないが、共有キューはsingletonである。

ネットワーク通信インジケータを正しく表示できる

“インクリメント”と”デクリメント”を使ってネットワークの処理をカウントして、ネットワーク通信インジケータ表示を行うサードパーティ製のクラスは沢山存在する。しかし、MKNetworkKitは共有キューのoperationCountをオブザーブ(KVO)する事で、自動的にネットワーク通信インジケータを表示する。開発者として、あなたはもうネットワーク通信インジケータについてなにも心配しなくていい。

#!objectivec
if (object == _sharedNetworkQueue && [keyPath isEqualToString:@"operationCount"]) {

    [UIApplication sharedApplication].networkActivityIndicatorVisible =
    ([_sharedNetworkQueue.operations count] > 0);
}

自動キューサイジング

前節の続きだが、モバイル・ネットワークでは3つ以上の同時接続は出来ないと言った。だから、3G回線の時はキューのサイズは2に設定するべきだ。MKNetworkKitは自動的にキューのサイズを調整してくれる。もしWi-Fi回線ならば自動でキューのサイズを6にしてくれる。この技術によって、3G経由でのサムネイル画像の読み込みをしている時にとてつもないパフォーマンスの恩恵が得られるだろう。

自動キャッシュ

MKNetworkKitは全ての”GET”リクエストを自動的にキャッシュする。もし同じリクエストを作成したら、MKNetworkKitはほぼすぐさま完了ハンドラをキャッシュされたレスポンスと共に呼び出す。また、リモートサーバに対してはもう一度リクエストを送る。サーバのデータを取得したら、完了ハンドラは新しいレスポンスデータと共にもう一度呼び出される。つまり、あなたはキャッシュのハンドリングを手動でやらなくていいという事だ。やるべき事は以下のメソッドを呼び出すだけだ。

#!objectivec
[[MKNetworkEngine sharedEngine] useCache];

もし必要なら、MKNetworkEngineのサブクラスのメソッドをオーバーライドして、キャッシュ保存先ディレクトリやメモリ上に置くキャッシュのコストをカスタマイズできる。

オペレーション凍結

MKNetworkKitを使えば、ネットワーク処理の凍結機能を使える。例えばネットワーク接続性が失われた時、ネットワーク処理を凍結化するとその処理は自動的にシリアライズされて保留される。そして、またオンラインになったら処理が再実行される。twitterクライアントの下書き保存みたいな感覚だ。

あなたがつぶやきを投稿した時、その処理にfreezableを設定するとMKNetworkKitは自動的に凍結と再実行の配慮をするようになる。だから、一行足すだけでつぶやきが後で送信されるようになる。この機能はつぶやきのお気に入りへの追加や、Googleリーダークライアントからのシェアの投稿とか、Instapaperにリンクを追加するといった事にも有効だ。

訳注: ネットワーク処理がfreezeされた場合、再実行されても完了ハンドラは呼び出されない。
NSNotificationで呼び出されるようにする予定との事

似た複数のリクエストに対して正確に一度だけ処理する

サムネイル画像を読み込む時、その画像毎にリクエストを作成し続けるだろう。しかし実際には、画像へのユニークなURLの数だけ呼び出すだけでよい(同じURLに対して毎回呼び出す必要は無い)。MKNetworkKitでは、同じURLに対する複数のGETリクエストは、実際には一度だけしか実行されない。MKNetworkKitはPOSTリクエストをキャッシュするほど賢くはない。

画像キャッシュ

MKNetworkKitはサムネイル画像のキャッシュをシームレスに出来る。少しだけメソッドをオーバーライドするだけで、メモリ内キャッシュはいくつまでにするべきか、キャッシュはどこに保存するべきかを設定できる。この作業は完全にオプションだ。

パフォーマンス

一言。スピード。MKNetworkKitはシームレスにキャッシュする。メモリ警告が発生した時にキャッシュディレクトリに保存される事を除けば、NSCacheと同じように働く。

Objective-C ARCへの完全対応

普通は新しいプロジェクトに対して新しいネットワーク・フレームワークを選ぶ。MKNetworkKitは既存のフレームワークを置き換えるためのものではない(出来るけど、それはうんざりするものだ)。新しいプロジェクトでは、ほとんどの場合いつもARCを有効にしたいだろう。MKNetworkKitはARCに完全対応しているたぶん唯一のネットワーク・フレームワークだ。ARCベースのメモリ管理はnon-ARCベースのメモリ管理のコードよりたいてい爆速だ。

使い方

OK、自画自賛はこれぐらいにして、使い方を説明しよう。

MKNetworkKitの追加

  1. MKNetworkKitディレクトリをプロジェクトにドラッグ
  2. CFNetwork.Framework, SystemConfiguration.framework, Security.frameworkを追加
  3. MKNetworkKit.hをPCHファイルに追記
  4. もしiOSアプリならばNSAlert+MKNetworkKitAdditions.h を削除
  5. もしMacアプリならばUIAlertView+MKNetworkKitAdditions.h を削除

これで完了。5つのファイルで行ける。パワフルなネットワーク・キットだ。

MKNetworkKitのクラス

  1. MKNetworkOperation
  2. MKNetworkEngine
  3. 様々なヘルパークラス(AppleのReachability)とカテゴリ

シンプルさは重要だ。Appleは実際のネットワーク処理のコードを書く面倒な作業をやってくれた。サードパーティのネットワーク・フレームワークが提供したのは、キャッシュの仕組みとキュー・ベースのエレガントなネットワーク処理だ。私は、どのサードパーティのフレームワークも10クラス未満であるべきだと思っている(ネットワークだろうがUIKitの後継だろうが)。もっと肥大化している。Three 20ライブラリは肥大化したものの代表例で、ほかにはShareKitなどがある。たぶんいい事だ。でも巨大で肥大している。ASIHTTPRequestかAFNetworkingはRESTKitと違って無駄が無くて軽い。JSONKitはTouchJSON(あとはTouchCodeライブラリとか)と違って軽い。たぶん自分だけだけど、私は三つ以上のサードパーティのライブラリを自分のソースコードに持ってくる事は出来ない。

巨大なフレームワークの問題は、内部処理が理解しにくい事とカスタマイズ性に欠けている事だ。拙作のフレームワーク(In-app-purchases向けのMKStoreKit)はいつもめちゃくちゃ簡単だし、MKNetworkKitも同様だと思っている。MKNetworkKitを使うために、必要な知識はMKNetworkOperationとMKNetworkEngineが出しているメソッドだけだ。MKNetworkOperationはASIHTTPRequestクラスに似ている。このクラスはNSOperationのサブクラスで、リクエストとレスポンスクラスを包含している。あなたは、MKNetworkOperationをネットワーク処理毎に作ればよい。

MKNetworkEngineは疑似singletonのクラスで、アプリ内でネットワーク処理のキューを管理する。疑似の厳密な意味は、単純なリクエストではMKNetworkEngineメソッドを直接使わなければならない事だ。もっとパワフルにカスタマイズする場合は、このクラスのサブクラスを作ればよい。それぞれのMKNetworkEngineは自身にReachabilityオブジェクトを持っている。このオブジェクトは、サーバへの到達性をチェックして通知してくれるものだ。MKNetworkEngineのサブクラスを作るのは、ユニークなRESTリクエストを毎回送る時などだ。疑似singletonの意味は、どのサブクラスで作られたリクエストも、同じ一つのキューに入る点である。

MKNetworkEngineはCoreDataのmanagedObjectContextクラスのようにアプリケーションのデリゲートに保持させる事が出来る。MKNetworkKitを使う時、ネットワーク処理を論理的にグループ化するためにMKNetworkEngineのサブクラスを作る。つまり、Yahoo関連のメソッド、Facebook関連のメソッドといったような分け方の単位でサブクラスにメソッドを記述する。以下に、このフレームワークを使った3つの例を示す。

Example 1: Yahoo! financeから為替情報を取ってくる”YahooEngine”を作る

ステップ1: MKNetworkEngineを継承したYahooEngineを作成しよう

MKNetworkEngineのinitメソッドはホスト名とカスタムヘッダを引数に取る。カスタムヘッダはオプションなのでnilに出来る。もし自前のRESTfulなサーバを書いているのなら、クライアントのアプリのバージョンとその他のクライアントIDみたいなデータを付け加える事を検討するとよいでしょう。

#!objectivec
NSMutableDictionary *headerFields = [NSMutableDictionary dictionary];
[headerFields setValue:@"iOS" forKey:@"x-client-identifier"];

self.engine = [[YahooEngine alloc] initWithHostName:@"download.finance.yahoo.com"
                                 customHeaderFields:headerFields];

Yahooはx-client-identifierをヘッダに含ませる事を求めていないので注意されたい。上記のサンプルコードは機能の説明のために見せているだけだ。

ARCなので、エンジンのインスタンスを保持するかどうかはあなた次第だ。

もしMKNetworkEngineのサブクラスを作ったなら、Reachabilityの実装は既に終えている。予期せぬ理由でサーバがダウンしてホストに到達出来なくなっても、リクエストは自動的にキューに追加されて、凍結される。詳しい説明は後の項を参照されたい。

ステップ2: エンジンクラスを定義しよう

為替レートを取得するためにYahooエンジンにメソッドを追加しよう。エンジンのメソッドはビューコントローラから呼び出される。いいデザインは、エンジンクラスが呼び出し側のクラスに対してURL/HTTPHeaderを露出しない事だ。ビューはURLのエンドポイントとパラメータを”知る”べきではない。つまり、Yahooエンジンのメソッドに渡されるパラメータは通貨と単位であるべきだ。このメソッドの戻り値はdouble型の為替レートの値で、取得時のタイムスタンプが望ましい。ネットワーク処理は同期的に行われないので、これらの結果の値はblocksで返す必要がある。例を示そう。

#!objectivec
-(MKNetworkOperation*) currencyRateFor:(NSString*) sourceCurrency
                            inCurrency:(NSString*) targetCurrency
                          onCompletion:(CurrencyResponseBlock) completion
                               onError:(ErrorBlock) error;

親クラスのMKNetworkEngineは以下のような三種類のblocksのメソッドを定義している。

#!objectivec
typedef void (^ProgressBlock)(double progress);
typedef void (^ResponseBlock)(MKNetworkOperation* operation);
typedef void (^ErrorBlock)(NSError* error);

YahooEngineクラスでは、新しい種類のブロックを使う。CurrencyResponseBlockは為替レートを返す。定義は以下のようになる。

#!objectivec
typedef void (^CurrencyResponseBlock)(double rate);

たいていのアプリでは、このように専用のブロックを定義してデータをビューコントローラに渡す。

ステップ3: データ処理

サーバから取得したJSONやXMK、バイナリのplistなどの形式のデータを変換する処理はエンジンクラスの中で行われるべきだ。繰り返すが、ビューコントローラをこのようなタスクから解放してあげよう。エンジンは妥当なモデルオブジェクトまたはその配列に変換された後のものを返すべきだ。JSON/XMLをモデルに変換する処理をエンジン部で行おう。

これでエンジンの設計は完了だ。ほとんどのネットワーク・フレームワークはこのような関係性分離を強制しない。しかし我々はそうする。あなたを心配しているから 🙂

ステップ4: メソッドの実装

為替を計算するメソッド実装の詳細について考えよう。

Yahooから通貨の情報を取ってくる事はGETリクエストの作成と同じぐらいシンプルだ。与えられた通貨ペアに対してURLを生成するマクロを以下に示す。

#!objectivec
#define YAHOO_URL(__C1__, __C2__) [NSString stringWithFormat:@"d/quotes.csv?e=.csv&f=sl1d1t1&s=%@%@=X", __C1__, __C2__]

エンジンクラス内で記述するメソッドは以下の順番で処理する。

  1. 与えられた引数からURLを作成する
  2. リクエスト用のMKNetworkOperationオブジェクトを作る
  3. メソッドに渡すパラメータを設定する
  4. 完了ハンドラとエラーハンドラをオペレーションオブジェクトに追加する(完了ハンドラはレスポンスデータの処理とモデルへの変換を行う場所)
  5. 必要に応じて、プログレスハンドラを追加する
  6. もしネットワーク処理がファイルのダウンロードなら、ダウンロードストリームを設定しよう
  7. 処理が完了した時、レスポンスを処理して呼び出し元にブロックでデータを返そう

コードにすると以下のようになる。

#!objectivec
MKNetworkOperation *op = [self operationWithPath:YAHOO_URL(sourceCurrency, targetCurrency)
                                          params:nil
                                      httpMethod:@"GET"];

[op addCompletionHandler:^(MKNetworkOperation *completedOperation)
 {
     DLog(@"%@", [completedOperation responseString]);

     // do your processing here
     completionBlock(5.0f);

 }
 errorHandler:^(MKNetworkOperation *completedOperation, NSError *error) {

     errorBlock(error);
 }];

[self enqueueOperation:op];

return op;

上記のコードは呼び出すURLを生成して、MKNetworkOperationオブジェクトを作成する。完了ハンドラとエラーハンドラを設定した後、親クラスのenqueueOperationメソッドを呼び出してキューにオペレーションを追加している。最後に、オペレーションオブジェクトへの参照を返している。ビューコントローラはこのオブジェクトを保持して、もしビューコントローラのヒエラルキーから外された時にキャンセルするようにするべきだ。なので、viewDidAppearなどでエンジンのメソッドを呼び出して、viewWillDisappearでキャンセルしよう。ネットワーク処理のキャンセルは、続いて表示されるビューで実行されるネットワーク処理のためにキューを解放する。(モバイルネットワークでは2つしかキューが無い事を覚えておこう。無駄なネットワーク処理を極力減らす事はアプリのスピード向上に繋がる)

ビューコントローラは適宜プログレスハンドラを設けて、進捗に応じてUIを更新できる。これは以下のようにして実現する。

#!objectivec
[self.uploadOperation onUploadProgressChanged:^(double progress) {

    DLog(@"%.2f", progress*100.0);
    self.uploadProgessBar.progress = progress;
}];

MKNetworkEngineは単なるURLに対するオペレーションオブジェクトを作るために便利なメソッドを用意している。なので、最初の行のコードは以下のようにも書ける。

#!objectivec
MKNetworkOperation *op = [self operationWithPath:YAHOO_URL(sourceCurrency, targetCurrency)];

URLには自動的に、エンジンの初期化時に指定したホスト名がプリフィックスとして追加される事に注意されたい。

POST、DELETEやPUTメソッドのリクエストはhttpMethodパラメータを変えるだけで簡単に津売れる。MKNetworkEngineはこのような便利メソッドをもっと持っている。ヘッダファイルを読んでほしい。

Example 2: TwitPicみたいなところに画像をアップロードする例

画像のアップロードにはデータをmulti-partでエンコードする必要がある。MKNetworkKitはASIHTTPRequestのデザインパターンを参考にしている。MKNetworkOperationの addFile:forKey: を呼び出すことで、multi-partとして簡単にファイルを”添付”できる。MKNetworkOperationはNSDataオブジェクトからでも画像を追加できる便利メソッドを持っている。つまり、addData:forKey: メソッドでNSDataオブジェクトから直接サーバに画像をアップロードできる。(カメラで撮った写真をアップロードする時に使えるね)

Example 3: ファイルをローカルのディレクトリにダウンロードする(キャッシュする)

MKNetworkKitを使えば超簡単にiPhone内にダウンロードしたファイルを保存できる。MKNetworkOperationのoutputStreamを以下のように設定する。

#!objectivec
[operation setDownloadStream:[NSOutputStream
    outputStreamToFileAtPath:@"/Users/mugunth/Desktop/DownloadedFile.pdf"
                      append:YES]];

複数のアウトプット・ストリームをオペレーションオブジェクトに設定する事で、複数の場所に同じファイルを保存する事もできる。(例えば、一方はキャッシュ用で、もう一方は作業用ディレクトリなど)

Example 4: サムネイル画像をキャッシュする

画像のダウンロードでは、相対パスではなくホスト名を含むフルURLをを与える必要があるだろう。MKNetworkEngineはこれをするための便利メソッドを持っている。operationWithURLString:params:httpMethod:を呼び出して、フルURLのオペレーションオブジェクトを作成できる。MKNetworkEngineは賢くて、同じURLへの複数のGETリクエストをひとまとめにして、処理が完了したら全てのblocksに通知する。これは画像取得に対してスピードをめちゃくちゃ改善する。

MKNetworkEngineのサブクラスを作って画像のキャッシュディレクトリとキャッシュコストをオーバーライドできる。別にカスタマイズの必要が無ければ、MKNetworkEngineのメソッドをそのまま呼び出してダウンロードすればよい。そのまま使う事をお勧めする。

レスポンスのキャッシュ

MKNetworkKitは標準で全てのリクエストをキャッシュする。やるべき事はそのキャッシュ機能をオンにするだけ。GETリクエストを実行する時、もしレスポンスが前にキャッシュされていたら、完了ハンドラがすぐさまキャッシュと共に呼び出される。キャッシュが渡されたかどうかを確認するには、isCachedResponseメソッドを呼び出す。以下に例を示す。

#!objectivec
[op addCompletionHandler:^(MKNetworkOperation *completedOperation)
 {
     if([completedOperation isCachedResponse]) {
         DLog(@"Data from cache");
     }
     else {
         DLog(@"Data from server");
     }

     DLog(@"%@", [completedOperation responseString]);
 } errorHandler:^(MKNetworkOperation *completedOperation, NSError *error) {

     errorBlock(error);
 }];

ネットワーク処理の凍結

ほぼ間違いなく、MKNetworkKitの最も面白い機能はネットワーク処理の凍結だろう。必要なのはオペレーションオブジェクトの凍結機能をオンにする事だけだ。それだけ!

#!objectivec
[op setFreezable:YES];

凍結可能なオペレーションオブジェクトはネットワークが切断された時に自動的にシリアライズされて、接続が復帰したら再試行される。例えば、オフライン時につぶやきをふぁぼると、オンラインになった時に実行されるというような事が出来る。凍結されたオペレーションオブジェクトは、アプリがバックグラウンドに入ってもディスクに残る。そしてアプリが復帰した後器に自動的に実行される。

MKNetworkOperationの便利メソッド

MKNetworkOperationはレスポンスデータの解釈のために以下のような便利メソッドを用意している。

  1. responseData
  2. responseString
  3. responseJSON
  4. responseImage
  5. responseXML
  6. error

ネットワーク処理が完了した後にレスポンスにアクセスする時に使える。レスポンスデータのフォーマットが不正な場合、これらのメソッドはnilを返す。例えば、responseImageを呼び出してもレスポンスデータがHTMLだったらnilが返る。responseDataだけは内容のフォーマットに関わらず必ず正しいものが返される。もし中身のタイプが事前に分かっている場合に、他のメソッドを使おう。

便利マクロ

DLogとALogマクロをStackoverflowから取ってきたけど、そのソースがもう見つからない。もし書いた人を知っていたら教えてください。

GCDについて

GCDは意図的に使っていない。ネットワーク処理は自由に優先度付けが必要で、キャンセルできる必要がある。GCDはNSOperationQueueより効果的だけど、これが出来ない。GCDベースのキューはネットワーク処理に向かないのでお勧めしない。

ドキュメント

ヘッダファイルにコメントを書いていて、Apple製のheaderdocで生成できるようにしようとしている。それまでは、コードを読んで欲しい。

ソースコード

MKNetworkKitのソースコードとそのデモアプリケーションはGithubに挙げられている。
MKNetworkKit on Github

機能の要望

私に直接メールしないで欲しい。Githubにissueを作ってほしい

ライセンス

MKNetworkKitはMITライセンスです。

コピーライトをアプリに付記すれば、全てのソースコードはあなたのアプリでタダで使える。aboutページみたいなあいまいなページに小さくでいいから。

フリーライセンスの取得はリクエストでお応えします。mknetworkkit@mk.sgまで。


Mugunth
訳: noradaiko

“iOS Framework:MKNetworkKitの概要” への7件のフィードバック

コメントを残す

以下に詳細を記入するか、アイコンをクリックしてログインしてください。

WordPress.com ロゴ

WordPress.com アカウントを使ってコメントしています。 ログアウト /  変更 )

Google フォト

Google アカウントを使ってコメントしています。 ログアウト /  変更 )

Twitter 画像

Twitter アカウントを使ってコメントしています。 ログアウト /  変更 )

Facebook の写真

Facebook アカウントを使ってコメントしています。 ログアウト /  変更 )

%s と連携中