Techracho

NSURLConnectionの同期通信でタイムアウトを設定したい(2)

このエントリーをはてなブックマーク Share
2010.01.11    馬場   タグ: , —    baba   

前回の問題ですが、自己解決しました。

今のメソッドを抜けずに現在のスレッドに溜まっている処理を処理するには、NSRunLoopのrunを使えば良いということでした。
つまり、
[[NSRunLoop currentRunLoop]run];
とやれば、NSURLConnectionによって呼ばれるconnectionなどが実行されます。

ただし、runは無限ループになって永久に処理し続ける(処理がなければ待っている)ので、別スレッドで実行して、目的を達成したらスレッドごと終了してしまう必要があります。

そこで、別スレッドでNSURLConnectionのconnectionWithRequestを実行し、そのスレッド内でNSRunLoopのrunを実行します。
そして、親スレッドでwhileループで監視しつつ、responseが返ってきたらスレッドを破棄すればOKです。

コードにしてみると以下のような感じです。

@implementation TestConnector

// これを外部から呼ぶ
- (NSString *)sendRequest:(NSString *)url {
	NSURLRequest *request = [NSURLRequest requestWithURL:url];
	[request setTimeoutInterval:3.0]; //3秒で反応長ければエラーにする

	//別スレッドで接続
	_response = nil; //_responseはvolatile指定したインスタンス変数
	NSThread *th = [[NSThread alloc]initWithTarget:self selector:@selector(sendRequestThread:) object:request];
	[th start];

	//responseが取得できるまで待つ
	while (_response == nil) {
		[NSThread sleepForTimeInterval:0.1];
		NSLog(@"sleep...");
	}

	[th cancel];
	[th release];
	[request release];
	return _response;
}

// リクエストを送信し、受信が完了するまで待つスレッド
- (void)sendRequestThread:(NSURLRequest *)request {
	NSAutoreleasePool *pool = [[NSAutoreleasePool alloc]init];

	//接続し、受信完了まで無限ループを回す
	[NSURLConnection connectionWithRequest:request delegate:self];
	[[NSRunLoop currentRunLoop]run];

	[connection release]; //実際には↑が無限ループなので呼ばれないはず
	[pool drain];
}

- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
}

- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
	//データの保存処理などを行う
}

- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
	//ここで適切に_responseをセットする
	_response = @"おわったよ";
}

@end

手抜きしたので、メモリリークがあるかも知れません。

実際には、responseは何らかのオブジェクトになると思います。
その際は、単にconnectionDidFinishLoadingなどでコピーすると、メモリが解放されたときにエラーになったりするので、必要に応じてretainとautoreleaseを行わないとダメですね。

NSURLConnectionの同期通信でタイムアウトを設定したい

このエントリーをはてなブックマーク Share
    馬場   タグ: , —    baba   

NSURLConnectionを使う際、非同期通信を基本にすべきですが、反応速度が重要でない場合は同期でやってしまいたいこともあります。

ただし、sendSynchronousRequestを使うと、NSURLRequestで指定したtimeoutIntervalは無視されるようで、不正なURLを指定した場合などに強制的に30秒以上待たされます。

「同期通信で、タイムアウトを5秒くらいにしたラッパーを作りたい」と思い、非同期通信を行い、通信が完了するまでwhileループで待つようなことを試してみました。

response = nil; //responseはvolatile指定したインスタンス変数
[NSURLConnection connectionWithRequest:request delegate:self];
while (response == nil) {
	[NSThread sleepForTimeInterval:1]; //1行待ってみる
	NSLog(@"待っています・・・");
}

このようにして、データを受け取るconnectionメソッドでは、インスタンス変数responseに結果を格納するようにします。

・・・しかし、この方法では永久に「待っています・・・」と表示されて、いつまでたっても受信されません。
理由は、connectionWithRequest自体は別スレッドで実行されるものの、その結果となるconnectionはメインスレッドで実行されるためです。
つまり、whileループのあるメソッドが終了するまで、プールに溜まるだけでconnectionメソッドが実行されないということみたいです。

また、connectionWithRequestそのものを別スレッドにする方法では、NSThreadはjoinに相当するものが無いため、即座に親スレッドが終了し、connectionが呼ばれる前に子スレッドも終了されてしまいます。

残念ながら、pthreadでjoinするような力業を使わない限り、「同期通信で、タイムアウトを5秒くらいにしたラッパーを作りたい」という目標は実現が難しそうです。

どなたか良い解決案をお持ちでしたら、是非教えて下さい。

そもそもtimeoutIntervalが無視されたり、joinが無いなどというのはライブラリの欠陥な気がするのですが・・・

COPYRIGHT [C] 2009 BEYOND PERSPECTIVE SOLUTIONS LTD. ALL RIGHTS RESERVED.