プログラミングノート

一からものを作ることが好きなエンジニアの開発ブログです。

GTM HTTP Fetcherを利用した通信方法まとめ

ASIHTTPRequestが開発終了ということで、最近はGoogleが開発しているGTMHTTPFetcherを利用しています。


Best Album、FlickPicsともに利用していますが、シンプルなNSURLConnectionのラッパーなのでとても扱いやすく、ソースもそこまで大きくないので必要に応じて自分で改良することも難しくありません。


Introductionにある程度まとまっているのですが、画像アップロードを開発する際、multipartリクエスト周りでハマったので、これだけ知ってれば十分使えるでしょうというところと合わせてまとめてみました。

特徴

  • Simple to build; only one source/header file pair is required
  • Simple to use: takes just two lines of code to fetch a request
  • Callbacks are delegate/selector pairs or blocks
  • Flexible cookie storage
  • Caching of ETagged responses, reducing overhead of redundant fetches
  • Automatic retry on errors, with exponential backoff
  • Support for generating multipart MIME upload streams
  • Easy, convenient logging of http requests and responses
  • Fully independent of other projects

Download

アーカイブされていないので、SVNからチェックアウトします。

svn checkout http://gtm-http-fetcher.googlecode.com/svn/trunk/ gtm-http-fetcher-read-only

通信してみる

あとで利用するものと合わせて必要なライブラリをインポートします。

#import "GTMHTTPFetcher.h"
#import "GTMMIMEDocument.h"
#import "GTMHTTPFetcherLogging.h"


これだけで非同期通信完了です。お手軽すぎますね!
Blockも対応しているのでお好みに応じて。

- (void)http{
  GTMHTTPFetcher* fetcher = [GTMHTTPFetcher fetcherWithURLString:@"http://www.google.com"];
  [fetcher beginFetchWithDelegate:self 
                         didFinishSelector:@selector(requestDidComplete:finishedWithData:error:)];
}

- (void)requestDidComplete:(GTMHTTPFetcher *)fetcher 
          finishedWithData:(NSData *)data
                     error:(NSError *)error{
  if(error){
    NSLog(@"%@", [error description]);
    return;
  }
  NSString *html = [[[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding] autorelease];
  NSLog(@"%@", html);
}


POSTの場合は、postDataにパラメータをセットでOK。

NSData *postData = [@"name=value" dataUsingEncoding:NSUTF8StringEncoding];
[fetcher setPostData:postData];


リクエスト毎にユーザーデータを保持しておきたい場合はuserDataを利用します。

[fetcher setUserData:@"userData"];

自動リトライ

あまり利用する場面はないかもしれませんが、パラメータをセットしておくだけで自動的にリトライをかけてくれます。

fetcher.retryEnabled = YES;
fetcher.retrySelector = @selector(fetcher:willRetry:forError:);


リトライ実行前に処理を挟むことが可能です。

-(BOOL)fetcher:(GTMHTTPFetcher *)fetcher willRetry:(BOOL)suggestedWillRetry forError:(NSError *)error{
  NSLog(@"%@", [error description]);    
  return YES;
}

Notification

通信中かどうかユーザーに伝えるため、通信中はnetworkActivityIndicatorVisibleをYESにセットして、終わるとNOをセットしてとなりますが、通信開始/終了時にNotificationが飛んでくるのでここで切替できます。

- (void)http{
  NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
  [center addObserver:self selector:@selector(httpStart:) name:kGTMHTTPFetcherStartedNotification object:nil];
  [center addObserver:self selector:@selector(httpStop:) name:kGTMHTTPFetcherStoppedNotification object:nil];
    ...
}

- (void)httpStart:(NSNotification *)notification{
  [UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
}

- (void)httpStop:(NSNotification *)notification{
  [UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
}

multipart/form-dataの送信

画像ファイルなどを送信したい場合、GTMMIMEDocumentを利用してmultipartリクエストを生成できます。

- (void)upload{
  GTMMIMEDocument *doc = [GTMMIMEDocument MIMEDocument];
    
  // テキストデータ
  NSDictionary *header = [NSDictionary dictionaryWithObject:@"form-data; name=\"title\"" forKey:@"Content-Disposition"];
  NSData *body = [@"multipart sample" dataUsingEncoding:NSUTF8StringEncoding];
  [doc addPartWithHeaders:header body:body];
    
  // バイナリデータ
  NSMutableDictionary *imgHeader = [[NSMutableDictionary alloc] initWithCapacity:0];
  [imgHeader setObject:@"form-data; name=\"photo\"; filename=\"photo.png\"" forKey:@"Content-Disposition"];
  [imgHeader setObject:@"image/png" forKey:@"Content-Type"];

  NSData *imgBody = UIImagePNGRepresentation([UIImage imageNamed:@"photo.png"]);
  [doc addPartWithHeaders:imgHeader body:imgBody];
  [imgHeader release];
    
  // リクエスト生成
  NSInputStream *stream = nil;
  NSString *boundary = nil;
  unsigned long long len = 0;
    
  [doc generateInputStream:&stream
                                length:&len
                           boundary:&boundary];
    
  if (stream) {
    NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:@"http://www.sample.com"]];
    [request setValue:[NSString stringWithFormat:@"multipart/form-data, boundary=%@", boundary] forHTTPHeaderField:@"Content-Type"];
    [request setValue:[NSString stringWithFormat:@"%llu", len] forHTTPHeaderField:@"Content-Length"];
        
    GTMHTTPFetcher *fetcher = [GTMHTTPFetcher fetcherWithRequest:request];
    [request release];
        
    [fetcher setPostStream:stream];
    [fetcher beginFetchWithDelegate:self
                            didFinishSelector:@selector(httpRequestDidComplete:finishedWithData:error:)];
  }
}


アップロードの進捗を知りたい場合はsentDataSelectorをセットしておけば受け取れます。

- (void)upload{
  ..
  [fetcher setSentDataSelector:@selector(didSendBodyData:bytesWritten:totalBytesWritten:totalBytesExpectedToWrite:)];
  ..
}

- (void)didSendBodyData:(GTMHTTPFetcher *)fetcher 
                   bytesWritten:(NSInteger)bytesWritten 
            totalBytesWritten:(NSInteger)totalBytesWritten
 totalBytesExpectedToWrite:(NSInteger)totalBytesExpectedToWrite{
 
  double progress = (double)totalBytesWritten / (double)totalBytesExpectedToWrite;
  NSLog(@"%f", progress);
}


ちなみにダウンロードの進捗を知りたい場合はこんな感じで受け取れます。

- (void)http{
  ..
  [fetcher setReceivedDataSelector:@selector(fetcher:receivedData:)];
  ..
}
- (void)fetcher:(GTMHTTPFetcher *)fetcher receivedData:(NSData *)dataReceivedSoFar{
    NSLog(@"%d / %lld",  [dataReceivedSoFar length], [[fetcher response] expectedContentLength]);
}

ロギング

最後に、通信系の開発ではログ取得が必須ですね。LoggingEnableにしておくとアプリケーションディレクトリ配下にGTMHttpTest_log_newest.htmlというファイルが生成され、通信ログが全て記録されるので便利です。

[GTMHTTPFetcher setLoggingEnabled:YES];


HTMLはこんな感じ。



いかがでしょう?
最近ではすっかりデフォルトで利用するライブラリになっています。