iOS4時代の非同期HTTP通信

SDKで始めるiPad/iPhoneアプリ開発の勘所(4):iOS 4のSDKで、Twitterを使ったiPhoneアプリを作る (1/4) - @IT
SDKで始めるiPad/iPhoneアプリ開発の勘所(4):iOS 4のSDKで、Twitterを使ったiPhoneアプリを作る (2/4) - @IT
を読んだのだけど。
NSURLConnectionを使った非同期HTTP通信は、この記事のようにラッパークラスを書いて

  • NSMutableDataを内部で保持
  • 通信開始時に初期化、delegateメソッドdidReceiveDataを実装して細切れに渡されてくるNSDataを付け足してゆき、
  • 通信終了時、最終的に得られた合計のNSDataはNotificationなどを使って呼び出し元に通知、その後NSMutableDataを破棄

といったことをする必要があり、何かとめんどくさい。
大抵の場合は途中のdidReceiveDataで受け取る細かいNSDataなどは興味なく、"NSURLRequestを渡したら、その結果としてのNSDataを受け取り処理したい"というだけだと思います。あとエラーがあればそれは捕捉するとして。
NSURLConnectionを使わない方法として以前にlibcurlを使う方法を書いてみましたが、その他に"Grand Central Dispatch"を使って非同期でHTTP通信する方法もあるので紹介してみます。


HttpClient.h

@interface HttpClient : NSObject {
}

+ (void)request:(NSURLRequest *)request success:(void (^)(NSData *))onSuccess error:(void (^)(NSError *))onError;

@end

HttpClient.m

#import "HttpClient.h"

@implementation HttpClient

+ (void)request:(NSURLRequest *)request success:(void (^)(NSData *))onSuccess error:(void (^)(NSError *))onError {
    dispatch_queue_t q = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_async(q, ^{
        NSURLResponse *response = nil;
        NSError       *error    = nil;
        NSData *data = [NSURLConnection sendSynchronousRequest:request
                                             returningResponse:&response
                                                         error:&error];
        if (error) {
            onError(error);
        } else {
            onSuccess(data);
        }
    });
}

@end


こんなクラスメソッド一つ用意しておくだけ。呼び出す側は

#import "HttpClient.h"

...

    void (^onSuccess)(NSData *) = ^(NSData *data) {
        ...
    };
    void (^onError)(NSError *) = ^(NSError *error) {
        ...
    };
    NSURL *url = [NSURL URLWithString:@"http://example.com/"];
    NSURLRequest *request = [NSURLRequest requestWithURL:url];
    [HttpClient request:request success:onSuccess error:onError];

の用に記述する。


dispatch_async()を使うとその中のコードブロックは非同期で処理されるので、同期的通信を行うNSURLConnectionの"+sendSynchronousRequest:returningResponse:returningResponse:error:"メソッドを内部で使ってもアプリ全体としての処理はブロックされずに非同期で通信が行われる。これを使えば一気にNSDataを取得できるしエラーも補足できる。終了時の処理も、メソッドを定義しておいてその@selectorを渡したりNSNotificationを使ったりすることなく、あらかじめコールバックとしてのコードブロックを定義してそれを渡してやることで、余計な記述を減らせる。PerlでいうとまさにAnyEvent::HTTPのような使い方ができるわけです。


iOS 4(MacだとOS 10.6)からの機能なので3.x系のことを考えると使えないですが(NSOperationとか使えば近いことはできる…のかな?)、4向けのアプリとして考えるなら上記の方法はとても簡単に書けてラクに使えるので便利なのではないでしょうか。