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]); }