【iOS】HTTP非同期通信

年始からの風邪がまだ治ってなくてつらいです、はい。
世間様は三連休なんですねー。。

今日は軽めに。。非同期のHTTPリクエストです。

ググればいくらでも出てきますが、
通信にはNSMutableURLRequestとNSURLConnectionを使用します。
また、タイムアウトは詳しくは忘れたけど実装したときには
なんかの不具合で普通のやりかたじゃうまく効かない場合があったので自前で実装してます。
あんま使う用途がないので、GET/POSTしか実装してません!
レスポンス/POST時のbodyはJSON形式のみ対応!
そしてシングルトンでの実装となっております!
シングルトンについてはこちらから。。
http://devdevdev.hatenablog.com/entry/2014/01/03/022110


何年か前に作ったライブラリからの抜粋なので、最近ならこうしたほうがいいよ!とか意見があればコメントください!
では、サンプルコード!

APIClient.h

#import "APIResponseBase.h"

// リクエスト先のURLプレフィックス
#define API_URL_PREFIX @"http://www.hoge.jp/api/"

// タイムアウト(sec)
#define TIMEOUT_SEC 15

typedef enum {
    MethodTypeGet,
    MethodTypePost,
} MethodType;

typedef void (^APICompletionBlock)(BOOL isSuccess,  APIResponseBase *responseData);

@interface APIClient : NSObject<UIAlertViewDelegate>

// メソッドタイプ(GET or POST)
@property (nonatomic) MethodType methodType;

// URL
@property (nonatomic) NSString *url;

// パラメータ
@property (nonatomic) NSDictionary *params;

// HTTPヘッダー
@property (nonatomic) NSDictionary *httpHeaders;

// レスポンスクラスのオブジェクト
@property (nonatomic) Class responseClass;

+ (APIClient *)sharedInstance;

- (void)startHttpRequestWithCompletionBlock:(APICompletionBlock)completion;


APIClient.m

#import "APIClient.h"

@interface APIClient()

// ステータスコード
@property NSInteger statusCode;

// レスポンスデータ保持用
@property NSMutableData *receivedData;

// コネクション
@property NSURLConnection *connection;

// 通信完了時Blocks
@property (nonatomic, copy) APICompletionBlock completion;

@end

@implementation APIClient

+ (APIClient *)sharedInstance {
    static APIClient *instance;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        instance = [[APIClient alloc]initInstance];
    });
    return instance;
}

- (id)init {
    [self doesNotRecognizeSelector:_cmd];
    return nil;
}

- (id)initInstance {
    self = [super init];
    if (self) {
        [self clearParams];
    }
    return self;
}

/**
 * HTTPリクエストの開始処理
 **/
- (void)startHttpRequestWithCompletionBlock:(APICompletionBlock)completion {
    if (self.connection != NULL) {
        [self endConnection:NO];
        return;
    }
    self.completion = completion;

    // URLへのリクエスト用オブジェクト
    NSMutableURLRequest *request;    

    // URL文字列
    NSMutableString *urlString = [NSMutableString stringWithFormat:@"%@%@", API_URL_PREFIX, self.url];

    // GET
    if (self.methodType == MethodTypeGet) {
        if (self.params.count > 0) {
            [urlString appendString:@"?"];
            NSMutableArray *paramArray = [NSMutableArray array];
            for (NSString *key in self.params.allKeys) {
                [paramArray addObject:[NSString stringWithFormat:@"%@=%@", key, self.params[key]]];
            }
            [urlString appendString:[paramArray componentsJoinedByString:@"&"]];
        }
        request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:urlString]];
    }
    // POST
    else if (self.methodType == MethodTypePost) {
        request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:urlString]];

	NSError *error = NULL;
	NSData *body = [NSJSONSerialization dataWithJSONObject:self.params options:NSJSONWritingPrettyPrinted error:&error];
	[request setHTTPBody:body];
	[request setValue:[NSString stringWithFormat:@"%d", body.length] forHTTPHeaderField:@"Content-Length"];
	[request setHTTPMethod:@"POST"];
        [request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
    }
    
    // HTTPヘッダー
    for (NSString *key in self.httpHeaders.allKeys) {
        [request setValue:self.httpHeaders[key] forHTTPHeaderField:key];
    }
    
    self.connection = [[NSURLConnection alloc]initWithRequest:request delegate:self];
    [self.connection start];

    // ステータスバーにぐるぐる表示
    [UIApplication sharedApplication].networkActivityIndicatorVisible = YES;

    // タイムアウト
    [self performSelector:@selector(connectionTimeout) withObject:nil afterDelay:TIMEOUT_SEC];
}

/**
 * タイムアウトに設定した時間が経過すると呼び出されるメソッド
**/
- (void)connectionTimeout {
    [self endConnection:NO];
}

/**
 * 通信がなんらかの理由で失敗したときに呼び出されるdelegate
**/
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
    [self endConnection:NO];
}

/**
 * サーバーから応答があった場合に呼び出されるdelegate
 **/
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
    self.receivedData.length = 0;
    self.statusCode = ((NSHTTPURLResponse *)response).statusCode;
    if (self.statusCode != 200) {
        [self endConnection:NO];
    }
}

/**
 * サーバーからデータを受信したときに呼び出されるdelegate
 * 通信完了までに複数回呼び出されます
**/
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
    [self.receivedData appendData:data];
}

/**
 * データ受信が完了したときに呼び出されるdelegate
**/
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
    [self endConnection:YES];
}

/**
 * 通信の終了処理
**/
- (void)endConnection:(BOOL)isSuccess {
    // ステータスバーのぐるぐる非表示
    [UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
    
    // タイムアウト通知解除
    [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(connectionTimeout) object:NULL];
    
    // コネクション破棄
    [self.connection cancel];
    self.connection = NULL;
    
    if (isSuccess) {
        NSError *jsonError = NULL;
        NSMutableDictionary *dictionary = [NSJSONSerialization JSONObjectWithData:self.receivedData options:NSJSONReadingMutableContainers error:&jsonError];
        if (jsonError == NULL) {
            APIResponseBase *response = [[APIResponseBase alloc]initWithDictionary:dictionary];
            Class resClass = self.responseClass;
            [self clear];
            self.completion(YES, response);
        }
    }
    
    UIAlertView *alertView = [[UIAlertView alloc]initWithTitle:@"" message:@"通信に失敗しました" delegate:self cancelButtonTitle:@"閉じる" otherButtonTitles:@"リトライ", nil];
    [alertView show];
}

- (void)clear {
    self.params = [NSDictionary dictionary];
    self.httpHeaders = [NSDictionary dictionary];
    self.methodType = MethodTypeGet;
    self.responseClass = NULL;
    self.receivedData = [NSMutableData data];
}

- (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex {
    if (buttonIndex == 0) {
        [self clear];
        self.completion(NO, NULL);
    }
    else if (buttonIndex == 1) {
        [self startHttpRequestWithCompletionBlock:self.completion];
    }
}

@end


今回はコピペしても動きませんのでご注意を!w