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
	}


参考