NSURLSession を使って HTTP POST を行う
前回までは単純に URL に対して GET リクエストを行った。
ここでは POST メソッドを使用し、標準的なエンコード方式を使ってパラメータを送信する。
全体像
パラメータの設定からレスポンスを受け取るまでは、次のような流れとなっている。
クエリ文字列を生成する buildQueryWithDictionary: については後に解説している。フレームワークに含まれるメソッドに関してはマニュアルを参照のこと。
- (void) postSomeData { // 送信する URL とパラメータ NSString *url = @"http://example.com/path/to/resource"; NSDictionary *params = @{ @"name": "スズメ", @"scientific_name": "Passer montanus", @"性質": "かわいい", }; // 連想配列として与えられたパラメータをクエリ文字列に変換する NSData *query = [self buildQueryWithDictionary: params]; // リクエストの種類、ヘッダを設定する NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL: [NSURL URLWithString: url] cachePolicy: NSURLRequestUseProtocolCachePolicy timeoutInterval: 10.0]; [request setHTTPMethod: @"POST"]; [request setValue: @"application/x-www-form-urlencoded" forHTTPHeaderField: @"Content-Type"]; [request setValue: [NSString stringWithFormat: @"%lu", (unsigned long)[query length]] forHTTPHeaderField: @"Content-Length"]; [request setHTTPBody: query]; // 共有セッションを取得し、サーバにリクエストを行う NSURLSession *session = [NSURLSession sharedSession]; [[session dataTaskWithRequest: request completionHandler: ^(NSData *data, NSURLResponse *response, NSError *error) { // レスポンスが成功か失敗かを見てそれぞれ処理を行う if (response && ! error) { NSString *responseString = [[NSString alloc] initWithData: data encoding: NSUTF8StringEncoding]; NSLog(@"成功: %@", responseString); } else { NSLog(@"失敗: %@", error); } }] resume]; }
クエリ文字列を生成する
標準のフレームワークの中には HTTP の標準的なクエリ文字列を組み立てるメソッドを見つけられなかった。そのため次の方法で生成する。
// クエリ文字列を得る - (NSData *) buildQueryWithDictionary: (NSDictionary *)params { // 連想配列のキーと値をそれぞれ URL エンコードし、 key=value の形で配列に追加していく NSMutableArray *parts = [NSMutableArray array]; for (id key in params) { [parts addObject: [NSString stringWithFormat: @"%@=%@", [self encodeURIComponent: (NSString *)key], [self encodeURIComponent: (NSString *)[params objectForKey: key]]]]; } // それぞれを & で結ぶ NSString *queryString = [parts componentsJoinedByString: @"&"]; // NSURLRequest setHTTPBody: に渡せるよう NSData に変換する return [queryString dataUsingEncoding: NSUTF8StringEncoding]; } // JavaScript の encodeURIComponent() 相当 - (NSString *) encodeURIComponent: (NSString *)string { return (__bridge_transfer NSString *)CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, (CFStringRef)string, NULL, CFSTR(";:@&=+$,/?%#[]"), kCFStringEncodingUTF8); }
ただし、このメソッドは同じキーに複数のデータを渡す(そして受け取る側はそれを配列として扱う)ことには対応していない。もし必要なら改良してほしい。
JSON 形式のデータを受け取る
レスポンスが JSON 形式の場合は次のようにして、NSDictionary オブジェクトとして受け取れる。
... [[session dataTaskWithRequest: request completionHandler: ^(NSData *data, NSURLResponse *response, NSError *error) { // レスポンスが成功か失敗かを見てそれぞれ処理を行う if (response && ! error) { NSDictionary *responseData = [NSJSONSerialization JSONObjectWithData: data options: NSJSONReadingAllowFragments error: nil]; if (responseData) { NSLog(@"成功: %@", responseData); } else { NSLog(@"JSON 形式でない: %@", [[NSString alloc] initWithData: data encoding: NSUTF8StringEncoding]); } } else { NSLog(@"失敗: %@", error); } }] resume]; ...
JSON 中の値がどの型かは次のようにして調べられる。必要に応じて使うとよい。
if (responseData == nil) { // JSON でない } else if ([responseData isKindOfClass: [NSNull class]]) { // null } else if ([responseData isKindOfClass: [NSNumber class]]) { // Number/Boolean } else if ([responseData isKindOfClass: [NSString class]]) { // String } else if ([responseData isKindOfClass: [NSArray class]]) { // Array } else if ([responseData isKindOfClass: [NSDictionary class]]) { // Object }
参考
- iOS プログラミングメモ; HTTP を扱う (2013-06-18)
――NSMutableURLRequest の扱い方を解説 - JavaScriptのencodeURIComponent相当のメソッドのObjective-Cでの実装 (2012-08-17)
- ARC環境下でのCFURLCreateStringByAddingPercentEscapes()について (2013-11-04)
――Core Foundation で生成したオブジェクトの管理を ARC に委ねる方法 - Jsonレスポンスからbool値を取り出す方法 (2014-03)