NSOperationQueue+NSURLConnectionで通信

裏でネットワークを大量に捌きたいときにはNSOperationQueueを使います。 NSOperationQueueは登録されたNSOperationを順次実行してくれるクラスです。 幾つか検索すれば手法が出てきますが、自分の環境ではそれらが使えなかったのでこちらに書いてみます。 NSOperationのサブクラスでNSURLConnectionを使うポイントとしては

  • データ待ちの無限ループを作る
  • isConcurrentを設定する

です。

データ待ちの無限ループを作る

NSOperationではバックグラウンドでデータ受信するの待ってる状態だったとしても、main関数を抜けてしまえば処理が終了してしまいます。 処理が終了しないためには無限ループが必要です。 さらに、ただの無限ループではダメで、その無限ループの最中にデータが来たかどうかなどのシステムの処理をきちんと行なう必要があります。 つまり、

1
do { [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:1]]; } while ( _state == NetworkStateInProgress);

と書く必要があります。 beforeDateに[NSDate distantFuture]を指定しているブログがどっかにありましたが、これは危険です。 beforeDateはいつまでシステムの処理を行うかというパラメーターなので、[NSDate distantFuture]を指定すると下手したら処理が戻って来ません。 きちんと何秒後かを指定してあげたほうがいいでしょう。

isConcurrentを設定する

バックグラウンドでデータを受信したい時というのは大抵複数同時に行うことが覆いと思います。 並列処理をする時はisConcurrent関数でYESを返して上げる必要があります。

1
-(BOOL)isConcurrent{ return YES; }

ソースコード

NetworkOperation.hファイル

1
enum NetworkState{ NetworkStateNotConnected, NetworkStateInProgress, NetworkStateError, NetworkStateCanceled, NetworkStateFinished, }; typedef enum NetworkState NetworkState; @interface NetworkOperation : NSOperation { NetworkState _state; NSString \*_urlString; NSURLConnection \*_connection; NSMutableData \*_downloadedData; NSError \*_error; } -(id)initWithUrlString:(NSString *)urlString; @end

NetworkOperation.mファイル

1
#import "NetworkOperation.h" @implementation NetworkOperation -(id)initWithUrlString:(NSString *)urlString{ self = [super init]; if(self){ _urlString = [urlString retain]; } return self; } -(BOOL)isConcurrent{ return YES; } - (void)start{ if([self isCancelled] ){ return; } NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; NSURLRequest \*request = nil; if(_urlString){ NSURL \*url; url = [NSURL URLWithString:_urlString]; if(url){ request = [NSURLRequest requestWithURL:url]; } } if(!request){ _state = NetworkStateError; return; } _downloadedData = [[NSMutableData alloc] init]; _connection = [[NSURLConnection connectionWithRequest:request delegate:self] retain]; _state = NetworkStateInProgress; [pool release]; [super start]; } -(void)main{ if (_connection != nil) { do { [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:1]]; } while ( _state == NetworkStateInProgress); } } -(void)cancel{ [_connection cancel]; _state = NetworkStateCanceled; [_connection release]; _connection = nil; [super cancel]; } -(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data{ [_downloadedData appendData:data]; } -(void)connectionDidFinishLoading:(NSURLConnection *)connection{ _state = NetworkStateFinished; [_connection release]; _connection = nil; } -(void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error{ _error = [error retain]; _state = NetworkStateError; [_connection release]; _connection = nil; } @end