HTTPステータスを判定して UIWebView を生成する

UIWebView はかんたんに HTTP 通信して Web ページを表示することができて便利なのだけれども、HTTP ステータスコードを見ていないため、iPhone 上に "500 Internal Server Error" とか出てくるわけです。これはカッコ悪い。

例えば、次のように UIWebView の loadRequest: を使った場合、通信エラー処理は webView:didFailLoadWithError: に委譲されるものの、HTTPサーバのエラー処理は委譲されず、処理するタイミングがありません。

- (void)viewDidLoad {
    UIWebView *wv = [[UIWebView alloc] initWithFrame: view.frame];
    wv.delegate = self;
    wv.scalesPageToFit = NO;
    [self.view addSubview:wv];
    
    [wv loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"http://www.deftrash.com/"]]];
    [wv release];
    
    [super viewDidLoad];
}

#pragma mark UIWebViewDelegate - (void)webView:(UIWebView *)wv didFailLoadWithError:(NSError *)error { NSLog(@"UIWebView::didFailLoadWithError"); }

仕方ないので、 NSURLConnection の delegate で制御します。 _conn と _data はそれぞれ、NSURLConnection と NSMutableData のインスタンス変数です。

- (void)viewDidLoad {
    NSURLRequest *req = [NSURLRequest requestWithURL:[NSURL URLWithString:@"http://www.deftrash.com/"]];
    _conn = [[NSURLConnection alloc] initWithRequest:req delegate:self startImmediately:YES];
    if (_conn) {
        _data = [[NSMutableData data] retain];
    }
}

-(void) connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response { NSInteger status = [(NSHTTPURLResponse *)response statusCode]; if (status != 200) { [_conn cancel]; // 本当はちゃんと NSError オブジェクトを作って渡すべき [self connection:connection didFailWithError:nil]; } else { [_data setLength:0]; } } -(void) connection:(NSURLConnection *) connection didReceiveData:(NSData *) data { [_data appendData:data]; } -(void) connection:(NSURLConnection *) connection didFailWithError:(NSError *) error { [_conn release]; _conn = nil; [_data release]; } -(void) connectionDidFinishLoading:(NSURLConnection *) connection { UIWebView *wv = [[UIWebView alloc] initWithFrame: self.view]; wv.scalesPageToFit = NO; [wv loadData:_data MIMEType:@"text/html" textEncodingName:nil baseURL:nil]; [_conn release]; _conn = nil; [_data release]; }

NSURLConnection の initWithRequest:delegate: を使うと TCP 通信のプロセスを委譲できるので、これを利用しています。なぜか delegate のプロトコルはありません。connection:didReceiveResponse: で最初のレスポンスを受けた段階で、ステータス判定して、正常(200) でなければ通信をキャンセルしています。 本来は switch 構文で、もっと細かくステータスごとの処理をするべきでしょう。せめて、400 や 500 番台だったらエラーというぐらいにはすべきでしょうね。

このあたりのコントロールは、実際にやってみると非常に面倒です。こうした非同期通信を複数スレッド走らせる場合は、きちんとキューイングしたり、インスタンスの使い回しをしないようにしたり、気にかけるポイントが多かったです。いやあ、途中でメモリの開放漏れなどで、10回くらいクラッシュしました。

こうして考えると ASIHTTPRequest は超絶便利です。ちょっとした HTTP 通信をするにはライブラリとして大きくなりすぎているのが気になっているものの、BSD ライセンスですし、選択肢として取れるのであれば積極的に使いたいものですね。

おつかれさまでした。

このエントリーのトラックバックURL
http://www.deftrash.com/admin/mt4/mt-tb.cgi/531