NSURLConnectionを使ってiPhoneとサーバー間でやり取りする実験です。まずはデータの受信から。
NSURLConnectionクラス
Cocoaでデータ通信を行う場合にはNSURLConnectionクラスを使う。NSURLConnectionクラスを使うと、同期/非同期でHTTP通信を行うことができる。指定したURLの内容を読み込む機能が実装されており、これを使ってデータの送受信を行う。
URLの読み込み
手始めにGoogleのトップページ(http://www.google.com/)の内容を読み込むテストをしてみる。
読み込む時に他の処理も停止してしまうと不便なので、非同期で読み込むように実装する。その場合の処理の流れは下記の用になる。
- どのURLを読み込みに行くか、というリクエストを作成する
- リクエストを元にHTTP通信を開始させる
- 通信に成功/失敗すると、指定したデリゲート先のオブジェクトのメソッドが呼ばれる
- そこでデータを解析するなどする
実際に実装してみると下記のようなコードとなる。
コントローラ
//
// network_connectionViewController.m
// network_connection
//
#import "network_connectionViewController.h"
@implementation network_connectionViewController
- (IBAction)openUrl:(id)sender{
MyNetworkConnector* connector = [[MyNetworkConnector alloc] initWithDelegate:self];
[connector openUrl:@"http://www.google.com/"];
}
- (void)receiveSucceed:(id)sender{
MyNetworkConnector* connector = (MyNetworkConnector*)sender;
NSArray* receivedLines = [connector.receivedString componentsSeparatedByString:@"\n"];
for (NSString* lineStr in receivedLines){
NSLog(@"%@", lineStr);
}
}
...略...
@end
MyNetworkConnector.h
//
// MyNetworkConnector.h
// network_connection
//
#import
@interface MyNetworkConnector : NSObject {
NSMutableData *receivedData;
NSStringEncoding encoding;
id _delegate;
NSString* receivedString;
}
@property (readonly) NSString* receivedString;
- (id)initWithDelegate:(id)delegate;
- (void)openUrl:(NSString*)urlString;
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response;
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data;
- (void)connectionDidFinishLoading:(NSURLConnection *)connection;
@end
MyNetworkConnector.mm
//
// MyNetworkConnector.mm
// network_connection
//
#import "MyNetworkConnector.h"
@implementation MyNetworkConnector
@synthesize receivedString;
- (id)initWithDelegate:(id)delegate{
if (self = [super init]) {
_delegate = delegate;
}
return self;
}
- (void)openUrl:(NSString*)urlString{
//受信したデータを保存するための変数を初期化します
receivedData = [[NSMutableData alloc] initWithLength:0];
if (receivedString != nil){
[receivedString release];
receivedString = nil;
}
//1.URLリクエストを作ります
NSURLRequest *req = [NSURLRequest requestWithURL:[NSURL URLWithString:urlString]
cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:60.0];
//2.HTTP通信を開始させます
NSURLConnection *conn = [[NSURLConnection alloc] initWithRequest:req delegate:self];
if (conn == nil) {
NSLog(@"error");
}
}
//サーバーからレスポンスを受信したときに呼ばれます
//大抵は1回だけ呼ばれますが、multipart/x-mixed-replaceの時は数回呼ばれる可能性があるようです(レアなケース)
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
NSLog(@"サーバーから応答がありました");
//あとで復元に使うため、エンコーディング方法を保存しておきます
//ここではShift_JISか、それ以外の場合はUTF-8でエンコードします
NSString *encodingName = [[response textEncodingName] lowercaseString];
if ([encodingName isEqualToString:@"shift_jis"] || [encodingName isEqualToString:@"sjis"]) {
encoding = NSShiftJISStringEncoding;
}else{
encoding = NSUTF8StringEncoding;
}
}
//データを受信したときに呼ばれます
//データは少しずつ送られてくるので何度も呼ばれます
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
NSLog(@"データを%dバイト受信しました",[data length]);
//現在の受信済みデータにマージします
[receivedData appendData:data];
}
//データ受信がすべて成功したときに呼ばれます
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
NSLog(@"%dバイトのデータの受信に成功しました", [receivedData length]);
//データを復元します
receivedString = [[NSString alloc] initWithData:receivedData encoding:encoding];
if (receivedString == nil){
NSLog(@"復元に失敗しました");
//復元に失敗したことを通知します
[_delegate performSelector:@selector(receiveFailed:)];
}else{
//受信完了したことを通知します
[_delegate performSelector:@selector(receiveSucceed:) withObject:self];
}
//不要になったデータを削除します
[receivedData release];
}
@end
なお、なぜか上記のコードでは一行目だけがうまくログに表示されないという不具合が発生している。受信データをファイルに書き出して確認する限りでは一行目もデータとしては正しく受信できている。BOMかなにかが原因だろうか?
参考資料:
本HPのソースを参考にさせていただいて、アプリをコーディングしていて気づいたのですが、1行目のデータがログに表示されないのは、receivedDataをインスタンス化する際に下記のように1バイト目を0埋めしている為です。
receivedData = [[NSMutableData alloc] initWithLength:1];
上記によって、1バイト目の文字列が壊れて表示できなくなり、二行目以降しかログ表示されなくなっています。
下記のようにインスタンス化すれば1行目のデータも正しく出力されます。
receivedData = [[NSMutableData alloc] initWithLength:0];
ご指摘ありがとうございます。
大分前に書いたコードで、今見てみると他にも色々気になる点があるのでそのうち直さなきゃな…。