UrlConnection を使ってみた

bonjoure とか、BSD ソケットだとか、通信の方法は色々とあるようですが、さっくり使う分にはNSUrlConnection がお手軽でよいと思いました.

テスト用にアプリケーションで利用するリソースを入れ替えるぐらいだったら、NSUrlConnection でも十分実用になるもんです.

そんなわけで、足跡をぺたぺたと..

非同期通信

接続する url を、NSURLRequest で定義して、NSURLResponse と NSError と一緒に、NSURLConnection に渡すだけで、通信が行える.
非同期通信(通信が終了するまで、処理が進まない)場合には、以下のコードだけでよい.

    NSString* a_url = @"http://hogehoge/hoge.jpg";
    NSURLRequest* a_request = [NSURLRequest requestWithURL:[NSURL URLWithString:a_url]];
    NSURLResponse* a_response;
    NSError* a_error;
    NSData* a_data = [NSURLConnection 
                         sendSynchronousRequest:a_request
                         returningResponse:&a_response
                         error:&a_error];

NSURLRequest には、直接 url を記述した文字列を渡すのではなく、NSURL として渡す.
NSURLConnection では、http://〜 というurlの形式だけではなく、ローカルファイルを示す、file://〜 という形でのアクセスも可能.
恐らく、ftp://〜 とか smb://〜 とかいう形式でも接続できると思うけど、当面利用しなさそうなので未検証.

NSURLResponse を利用して、サーバから返された情報にアクセスできる.

    NSLog( @"size = %d", [a_response expectedContentLength] ); // サイズの取得
    NSLog( [a_response MIMEType] );                            // mime-Type
    NSLog( [a_response textEncodingName] );                    // テキストエンコーディング

今回は特に利用しなかったので、取得したデータを NSLog で表示しただけ...

非同期通信

非同期通信の場合には、 delegate を用意する必要がある.

ヘルプを眺めたとき、 「delegate を指定する必要があるのは分かったんだけれども、プロトコルはどこに定義してあんだよっ」なんてことで悩んでいたんだけれども、プロトコルは Objective-C2.0 で追加された仕様なのかそんなもんは必要なかった.

単純に、デリゲートとしたオブジェクトに、必要な delegate の関数をぺたぺた書いていけばよい.

私は、適当に View に定義しておいた.

必要ありそうな delegate 関数(言い方あってるのか?)は、以下になる

/// サーバからレスポンスが送られてきたときのデリゲート
- (void)connection:(NSURLConnection *)i_connection didReceiveResponse:(NSURLResponse *)i_response;

/// サーバからデータが送られてきたときのデリゲート
- (void)connection:(NSURLConnection *)i_connection didReceiveData:(NSData *)i_data;

/// データのロードか完了した時のデリゲート
- (void)connectionDidFinishLoading:(NSURLConnection *)i_connection;

/// サーバからエラーが返されたときのデリゲート
- (void)connection:(NSURLConnection *)i_connection didFailWithError:(NSError *)i_error;

プロトコル部分で悩んだので、念のため View のヘッダーも貼っておくことにする.

@interface MainView
    : UIView
{
    UIImage* m_image;
    NSMutableData* m_data;
}

- (IBAction) getImage:(id)i_sender;

/// サーバからレスポンスが送られてきたときのデリゲート
- (void)connection:(NSURLConnection *)i_connection didReceiveResponse:(NSURLResponse *)i_response;

/// サーバからデータが送られてきたときのデリゲート
- (void)connection:(NSURLConnection *)i_connection didReceiveData:(NSData *)i_data;

/// データのロードか完了した時のデリゲート
- (void)connectionDidFinishLoading:(NSURLConnection *)i_connection;

/// サーバからエラーが返されたときのデリゲート
- (void)connection:(NSURLConnection *)i_connection didFailWithError:(NSError *)i_error;

@end

ほら、プロトコルなんて出てこない.

接続されると、適当なタイミングで connection:didReceiveData に受信した分だけのデータが渡されるので(つまり、ちぎれたデータが飛んでくる) NSMutableData なんかを利用して、データを保持しておけばよい.

実際の接続の方法は、次の通り

- (IBAction) getImage:(id)i_sender
{
    NSString* a_url = @"http://hogehoge/hoge.jpg";
    NSURLRequest* a_request = [NSURLRequest requestWithURL:[NSURL URLWithString:a_url]];

    [NSURLConnection connectionWithRequest:a_request delegate:self];
}

あとは、デリゲートが呼び出されるタイミングで、適切な処理をすればよい.

/// サーバからレスポンスが送られてきたときのデリゲート
- (void)connection:(NSURLConnection *)i_connection didReceiveResponse:(NSURLResponse *)i_response
{
    NSLog( @"size = %d", [i_response expectedContentLength] ); // サイズの取得
    NSLog( [i_response MIMEType] );                            // mime-Type
    NSLog( [i_response textEncodingName] );                    // テキストエンコーディング

    // データ蓄積用に NSMutableData を初期化
    m_data = [[NSMutableData alloc] init];
}

最初に呼び出されるのが、 connection:didReceiveResponse となる.
サーバ側がサボらずに、ここで適切なサイズを返してくれているんだったら、NSMutableData なんか使う必要は無い.

/// サーバからデータが送られてきたときのデリゲート
- (void)connection:(NSURLConnection *)i_connection didReceiveData:(NSData *)i_data
{
    [m_data appendData:i_data];
}

次に来るのは、 connection:didReceiveData が順当.
こいつは、適当な頻度で必要な回数呼ばれる点に注意する必要がある.
(繰り返しになるけれども、データは一度に来ない)

/// データのロードか完了した時のデリゲート
- (void)connectionDidFinishLoading:(NSURLConnection *)i_connection
{
    // 取得したデータを保存する
    NSString* a_home_dir = NSHomeDirectory();
    NSString* a_doc_dir = [a_home_dir stringByAppendingPathComponent:@"Documents"];

    NSFileManager* a_file_mgr = [NSFileManager defaultManager];
    [a_file_mgr changeCurrentDirectoryPath:a_doc_dir];

    [a_file_mgr createFileAtPath:@"unsync-image.jpg" contents:m_data attributes:nil];

    [m_data release];
    m_data = nil;
    
}

そして、取得の終了.
ここではファイルに書き込んでいるけれども、あとは好きにすればよい.

/// サーバからエラーが返されたときのデリゲート
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
    NSLog( @"失敗" );
    [m_data release];
    m_data = nil;
}

通信は、自分の都合だけでは成り立たないので、エラーも考えておきましょう.

まとめ

いつもながら、分かってしまえば簡単でした.
デリゲートが必要なのに、プロトコルが無いって状況に初めて遭遇したので、この点は良い勉強になりました.
結局のところ、ヘルプに書いてある delegate function の中から必要なものだけ実装すればいいのね.