[iPhone]サーバとHTTP通信する(同期通信・非同期通信/GET・POST)

はじめに

以前AndroidでサーバとHTTP通信する方法を書きましたが、その実装になぞらえてiPhoneでも同様のサンプルを作ろうと思います。汎用的な基底クラスを作成することで開発効率が向上することを切に願います。
[Android]サーバーと通信する方法

作成するクラスとその説明

リクエストパラメータクラス RequestParam abstract
⇒サーバに送信するデータを保持したクラス。業務や処理によって送信したいパラメータは異なってくるので、基本的には当該クラスのサブクラスを作成し、送信したいパラメータのカスタマイズを行って利用します
レスポンスパラメータクラス ResponseParam abstract
⇒サーバから受けたデータを保持したクラス。業務や処理によって受信したいパラメータは異なってくるので、基本的には当該クラスのサブクラスを作成し、受信したいパラメータのカスタマイズを行って利用します
リクエスト送信クラス Request
⇒リクエストパラメータをサーバに送信し、レスポンスパラメータをサーバから受けるためのクラス

RequestParam

RequestParam.h

#import <Foundation/Foundation.h>
#import "ResponseParam.h"

@interface RequestParam : NSObject {
    /** デバイスID */
    NSMutableString *deviceId;
    /** サーバURL */
    @public
    NSString *serverUrl;
    /** メッセージタイプ */
    @public
    NSString *messageType;
    /** yyyymmddhhmmss */
    @public
    NSString *dateTime;
    /** 接続タイムアウト */
    int connectionTimeout;
    /** データ取得のタイムアウト */
    int soTimeout;
}

/**
  * レスポンスパラメータを返す。
  */
- (ResponseParam *) getResponseParam;

/**
  * サーバに送信するパラメータを取得する。
  */
- (NSMutableDictionary *) getSendParameter;

@end

deviceIdとしてはUDIDではなくUUIDを利用するようにしましょう。
AppleにRejectされてしまします。

    NSMutableString *deviceId;

これはRequestParamをオーバーライドしたサブクラスに対応するレスポンスパラメータを返すためのメソッドです。あくまで、RequestParamとResponseParamを処理を共通化しただけの基底クラスなので個別の実装はサブに任せます。

- (ResponseParam *) getResponseParam;

Request.m内に実装されていますが、サーバに送信するパラメータをGenerateする実装をサブクラスでこちらに行います。
サーバに送信したい情報をキー バリューとしてNSMutableDictorinaryにセットする実装を書けばよいです。

- (NSMutableDictionary *) getSendParameter;

次に実装に移ります。(あくまでRequestParam.hの実装で前述しているサブクラスとは異なる)
RequestParam.m

#import "RequestParam.h"
#import "FrameworkConstants.h"

@implementation RequestParam

- (id) init {
    self = [super init];
    connectionTimeout = 60;
    soTimeout = 60;
    // ここでは適当な時間をセットしています。
    dateTime = @"yyyymmdd";
    return self;
}

- (ResponseParam *) getResponseParam {
    // サブクラスでオーバーライドしてください。
    return nil;
}

- (NSMutableDictionary *) getSendParameter {
    NSMutableDictionary *params = [[NSMutableDictionary alloc] init];
    [params setObject:messageType forKey:MESSAGE_TYPE];
    [params setObject:dateTime forKey:DATE_TIME];
    return params;
}

@end

ResponseParam

ResponseParam.h

#import <Foundation/Foundation.h>
#import "FrameworkConstants.h"

@interface ResponseParam : NSObject {
    /** HTTPステータスコード */
    NSInteger *httpStatusCode;
    /** エラーメッセージ */
    NSString *errorMessage;
    /** 生の電文 */
    NSMutableDictionary *rawData;
}

/**
  * 生の電文データを設定する。
  */
- (void) setRawData:(NSString *) str;

/**
  * データを解析してフィールドに設定する。
  */
- (void) setData;

/**
  * 通信終了後の処理。
  */
- (void) afterResponse;

@end

この実装は基本的に戻り電文の形式がかわらなければ同一の実装でOKです。
私が想定した電文の形式は「key1=value1&key2=value2&….&END」です。

- (void) setRawData:(NSString *) str;

これはサブクラスでの実装となります。さすがにマップに詰まっている状態だと実装が煩雑になるのでサブクラスにフィールドを定義し、そこに設定する実装を追加します。

- (void) setData;

通信終了後の個別の実装はここに記述します。

- (void) afterResponse;

ResponseParam.m

#import "ResponseParam.h"

@implementation ResponseParam

- (void) setRawData:(NSString *) str {
    NSArray *values = [str componentsSeparatedByString:AMPARSAND];
    rawData = [[NSMutableDictionary alloc] init];
    for (NSString *s in values) {
        if ([s isEqualToString:END]) {
            break;
        }
        NSArray *arr = [s componentsSeparatedByString:EQUAL];
        [rawData setObject:[arr objectAtIndex:1] forKey:[arr objectAtIndex:0]];
    }
}

- (void) setData {
    
}

- (void) afterResponse {
    // should not be use.
}

@end

Request

Request.h

#import <Foundation/Foundation.h>
#import "HattrickConstants.h"
#import "RequestParam.h"
#import "ResponseParam.h"

@protocol HttpMethodFinishDelegate;
@interface Request : NSObject {
    /** レスポンスデータバッファ */
    NSMutableData *buffer;
    /** リクエストパラメータ */
    RequestParam *requestParam;
    /** レスポンスパラメータ */
    ResponseParam *responseParam;
}

@property (nonatomic, assign) id <HttpMethodFinishDelegate> httpMethodFinishDelegate;

/**
  * POSTメソッドで非同期リクエストを送信する。
  */
-(void)sendAsyncPostRequest;

/**
  * POSTメソッドで同期リクエストを送信する。
  */
-(void)sendSyncPostRequest;

/**
  * GETメソッドで非同期リクエストを送信する。
  */
-(void)sendAsyncGetRequest;

/**
  * GETメソッドえ同期リクエストを送信する。
  */
-(void)sendSyncGetRequest;

/**
  * リクエストパラメータを設定する。
  */
-(void)setRequestParam:(RequestParam *) _requestParam;

/**
  * リクエストパラメータに対応するレスポンスパラメータを返す。
  */
-(ResponseParam *)getResponseParam;

/**
  * 同期リクエストを送信する。
  */
-(void)sendSyncRequest:(BOOL)post;

/**
  * 非同期リクエストを送信する。
  */
-(void)sendAsyncRequest:(BOOL)post;

@end

@protocol HttpMethodFinishDelegate <NSObject>

@optional
-(void)onHttpSyncMethodSuccess:(Request *)request;
-(void)onHttpSyncMethodFail:(Request *)request;
-(void)onHttpAsyncMethodSuccess:(Request *)request;
-(void)onHttpAsyncMethodFail:(Request *)request;
@end

非同期でPOST/GETリクエストを送信します。戻りはメソッド内ではなくコールバック(delegate)メソッドで受けてあげます。

-(void)sendAsyncPostRequest;
-(void)sendAsyncGetRequest;

同期でリクエストを送信します。戻りはメソッド内でうけるので画面が固まってしまうというユーザビリティの悪さでデメリットがありますが、アプリが起動する上で必要なものに関しては同期処理で受ける必要があるかと思います。非同期処理ほど使うことは少ないと思いますが。

-(void)sendSyncPostRequest;
-(void)sendSyncGetRequest;

上記のリクエスト送信メソッドはすべて以下の二つのメソッドに処理を委ねます。

-(void)sendSyncRequest:(BOOL)post;
-(void)sendAsyncRequest:(BOOL)post;

リクエスト送信前にリクエストパラメータを設定します。

-(void)setRequestParam:(RequestParam *) _requestParam;

リクエスト送信後(レスポンス受信後)にそのレスポンスの実態を使用したクラスから参照したい場合に利用します。

-(ResponseParam *)getResponseParam;

最後にプロトコルの宣言をしています。これは呼び出し元上位クラスに通信正常終了・異常終了を通知するためのコールバックです。

@protocol HttpMethodFinishDelegate <NSObject>
 @optional
-(void)onHttpSyncMethodSuccess:(Request *)request;
-(void)onHttpSyncMethodFail:(Request *)request;
-(void)onHttpAsyncMethodSuccess:(Request *)request;
-(void)onHttpAsyncMethodFail:(Request *)request;
@end

Request.mでこれらを実装してやることで、Javaでいうコールバックの実装が可能となります。

Request.m

#import "Request.h"

@implementation Request

@synthesize httpMethodFinishDelegate;

- (id) init {
    return self;
}

///////////////////////////////////////////////////////////////////////////////////////////////////
- (void) sendAsyncPostRequest {
    [self sendAsyncRequest:YES];
} 

///////////////////////////////////////////////////////////////////////////////////////////////////
-(void)sendSyncPostRequest {
    [self sendSyncRequest:YES];
}

///////////////////////////////////////////////////////////////////////////////////////////////////
-(void)sendAsyncGetRequest {
    [self sendAsyncRequest:NO];
}

///////////////////////////////////////////////////////////////////////////////////////////////////
-(void)sendSyncGetRequest {
    [self sendSyncRequest:NO];
}

///////////////////////////////////////////////////////////////////////////////////////////////////
- (void) setRequestParam:(RequestParam *)_requestParam {
    requestParam = _requestParam;
}

///////////////////////////////////////////////////////////////////////////////////////////////////
- (ResponseParam *) getResponseParam {
    if (responseParam) {
        return responseParam;
    } else {
        return [requestParam getResponseParam];
    }
}

/**
  * サーバからレスポンスがかえってきたときのコールバック。
  */
- (void) connection:(NSURLConnection *)conn didReceiveResponse:(NSURLResponse *)response {
    NSLog( @"size = %lld", [response expectedContentLength] );
    NSLog( @"%@", [response MIMEType]); 
    NSLog( @"%@", [response textEncodingName]);
    responseParam = [self getResponseParam];
}

/**
  * サーバからデータがかえってきたときのコールバック。
  */
- (void) connection:(NSURLConnection *)conn didReceiveData:(NSData *)receiveData {
    [buffer appendData:receiveData];
    NSString *message = [[NSString alloc] initWithData:buffer encoding:NSUTF8StringEncoding];
    [responseParam setRawData:message];
    
}

/**
  * サーバと接続できなかった場合のコールバック。
  */
- (void) connection:(NSURLConnection *)conn didFailWithError:(NSError *)error {
    NSLog(@"Connection failed: %@", [error localizedDescription]);
    [httpMethodFinishDelegate onHttpAsyncMethodFail:self];
}

/**
  * データのロードが完了したときのコールバック。
  */
- (void) connectionDidFinishLoading:(NSURLConnection *)conn {
    NSLog(@"Succeed! Received %d bytes of data.", [buffer length]);
    NSString *contents = [[NSString alloc] initWithData:buffer encoding:NSUTF8StringEncoding];
    NSLog(@"%@", contents);
    [responseParam afterResponse];
    [httpMethodFinishDelegate onHttpAsyncMethodSuccess:self];
}

-(void)sendAsyncRequest:(BOOL)post {
    NSString *httpMethod;
    if (post == YES) {
        httpMethod = @"POST";
    } else {
        httpMethod = @"GET";
    }
    NSURL *url = [NSURL URLWithString: requestParam->serverUrl];
    NSMutableString *value = [[NSMutableString alloc] init];
    NSMutableDictionary *values = [requestParam getSendParameter];
    
    NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url];
    [request setHTTPMethod:httpMethod];
    [request setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"content-type"];
    
    if (values) {
        for (id key in values) {
            [value appendString:key];
            [value appendString:EQUAL];
            [value appendString:[values objectForKey:key]];
            [value appendString:AMPARSAND];
        }
        NSData *requestData = [value dataUsingEncoding:NSUTF8StringEncoding];
        [request setHTTPBody: requestData];
    }else {
        NSLog(@"%@", requestParam->serverUrl);
    }
    
    NSURLConnection *conn = [NSURLConnection connectionWithRequest:request delegate:self];
    if (conn) {
        buffer = [[NSMutableData alloc] init];
    } else {
        UIAlertView *alert = [
                              [UIAlertView alloc]
                              initWithTitle : @"ConnectionError"
                              message : @"ConnectionError"
                              delegate : nil
                              cancelButtonTitle : @"OK"
                              otherButtonTitles : nil
                              ];
		[alert show];
    }
}

-(void)sendSyncRequest:(BOOL)post {
    NSString *httpMethod;
    if (post == YES) {
        httpMethod = @"POST";
    } else {
        httpMethod = @"GET";
    }
    NSURL *url = [NSURL URLWithString:requestParam->serverUrl];
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
    NSURLResponse *response = nil;
    NSError *error = nil;
    
    NSMutableString *value = [[NSMutableString alloc] init];
    NSMutableDictionary *values = [requestParam getSendParameter];
    [request setHTTPMethod:httpMethod];
    [request setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"content-type"];
    if (values) {
        for (id key in values) {
            [value appendString:key];
            [value appendString:EQUAL];
            [value appendString:[values objectForKey:key]];
            [value appendString:AMPARSAND];
        }
        NSData *requestData = [value dataUsingEncoding:NSUTF8StringEncoding];
        [request setHTTPBody: requestData];
    }else {
        NSLog(@"%@", requestParam->serverUrl);
    }
    
    NSData *data = [
                    NSURLConnection
                    sendSynchronousRequest : request
                    returningResponse : &response
                    error : &error
                    ];
    // error
	NSString *error_str = [error localizedDescription];
	if (0 < [error_str length]) {
        [httpMethodFinishDelegate onHttpSyncMethodFail:self];
		return;
	}
    // response
    NSString *result = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
    responseParam = [self getResponseParam];
	[responseParam setRawData:result];
    [responseParam afterResponse];
    [httpMethodFinishDelegate onHttpSyncMethodSuccess:self];
}

@end

最後に

次回はサンプルでも作成してみようかと思います。