2014.06.04
STAFF BLOG
スタッフブログ
TECHNICAL
テクログ
Mac,iPhone,Objective-C 全て利用歴6ヶ月の新米プログラマーが送る
自分が使って役立ったmethod&Tips集
シリーズ第12弾です。
今回は開始1ヶ月目くらいで挑戦した非同期通信についてです。
画面に表示するような動的部分が無いとは言えいきなり挑戦するなんて無謀でした。
しかし、始めたばかりで無謀かどうかの判断すら付かないのであちこち調べました。
当時ライブラリがあることすら知らなかったのですが、今なら AFNetworking, ASIHTTPRequest 当たりを調べて使いますねキット。
完成したのがこの簡単バージョン
よそ様の非同期通信のサンプルは画像持ってくる例が多いのですが、今回はサーバ上にあるphpで処理を行い結果を受け取るという前提です。
非同期通信なのに通信終了まで待ってます。これはサーバーからデータを受け取らないと画面だけ表示しても意味をなさないからです。
ではなぜ、同期通信しないのか。設計は複雑になりますが、これだけ覚えておけばやり方によって同期/非同期どっちも使えるからです。
実際にコーディングした際もデータが取得出来なかったときの処理がやりやすかったです。
というわけで、サーバープログラムから解説
blogasync1.php
?if(empty($_POST["input"])){
??$ar = NULL;
?} else {
??$_POST["input"];
??// 受け取った値とステータスを返す
??$ar[]=array("input"=>$_POST["input"],"ret"=>"OK");
??$ar[]=array("input"=>$_POST["input"],"ret"=>"OK-2");
?}
header("Content-Type: application/json; charset=utf-8"); //JSONファイルの出力
echo json_encode($ar, JSON_UNESCAPED_UNICODE); //JSON形式にして返す
続いてiOS側。Xcodeでフォームに配置したボタンをAction接続してあります
NSURLConnectionDelegate も利用します
外部ライブラリでくるくる回る画像を利用してます。
https://github.com/samvermette/SVProgressHUD
をダウンロードして FinderからSVProgressHUDディレクトリを自分のプロジェクトにポイっと入れて下さい
ViewController.h
#import
@interface ViewController : UIViewController
- (IBAction)async:(id)sender;
@end
ViewController.m
#import "ViewController.h"
#import "SVProgressHUD.h"
@interface ViewController (){
NSString *connectStatus_;
NSMutableData *mData_; // json
NSMutableArray *cellList_;
}
@end
@implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
?// Do any additional setup after loading the view, typically from a nib.
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#pragma mark svr connection
// 非同期通信でgroup 1record 取得
- (void)getJson
{
// 通信中表示
[SVProgressHUD showWithMaskType: SVProgressHUDMaskTypeClear];
connectStatus_ = @"start";
// login id
NSString *query = [NSString stringWithFormat:@"input=%@", @"test-test"];
NSData *queryData = [query dataUsingEncoding:NSUTF8StringEncoding];
// 各自のサーバのURLに置き換えて下さい。
NSString *url = @"http://localhost/blogasync1.php";
NSMutableURLRequest *request = [[NSMutableURLRequest alloc]init];
[request setTimeoutInterval:30]; //タイムアウトを30秒に設定
[request setURL:[NSURL URLWithString:url]];
[request setHTTPMethod:@"POST"];
[request setHTTPBody:queryData];
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
[NSURLConnection connectionWithRequest:request delegate:self];
NSLog(@"async connection");
while ([connectStatus_ isEqualToString:@"start"]) {
// while 内で一旦 NSRunLoop へ制御戻してやる。runUntilDateはモードとしてNSDefaultRunLoopModeを用いる
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.5]]; //0.5sec
}
if ([connectStatus_ isEqualToString: @"OK"]) {
NSError *error = nil;
//NSDictinaryがNSArrayになって返ってきます(id の中に入る)
id json = [NSJSONSerialization JSONObjectWithData:mData_ options:NSJSONReadingAllowFragments error:&error];
if(!error) {
if (json == [NSNull null]) {
return;
}
cellList_ = [NSMutableArray array];
NSUInteger cnt = [json count];
for (int idx=0; idx<cnt; idx++)="" {<br=""> [cellList_ addObject:json[idx][@"input"]];
[cellList_ addObject:json[idx][@"ret"]];
}
}
}
// 通信中表示を消す
[SVProgressHUD dismiss];
}
//通信開始時に呼ばれる
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
NSLog(@"receive response");
mData_ = [NSMutableData data];
}
//通信中常に呼ばれる
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
NSError *jsonError;
NSMutableArray *returnedData = [[NSMutableArray alloc] init];
returnedData = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&jsonError];
NSLog(@"%@", returnedData);
//通信したデータを入れていきます
[mData_ appendData:data];
}
//通信終了時に呼ばれる
- (void)connectionDidFinishLoading: (NSURLConnection *)connection
{
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
NSLog(@"finish connection");
connectStatus_ = @"OK";
// NSError *error = nil;
// //NSDictinaryがNSArrayになって返ってきます(id の中に入る)
// id json = [NSJSONSerialization JSONObjectWithData:self.mData options:NSJSONReadingAllowFragments error:&error];
// if(!error) {
// //NSLog(@"%@",json);
// connectStatus_ = @"OK";
// } else {
// NSLog(@"json error-1");
// connectStatus_ = @"NG";
// }
}
//通信エラー時に呼ばれる
-(void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
//エラー処理を書く
NSLog(@"err %@",[error localizedDescription]);
// 通信中表示を消す
[SVProgressHUD showErrorWithStatus:@"接続に失敗しました"];
connectStatus_ = @"NG";
// エラー情報を表示する。
// objectForKeyで指定するKeyがポイント
NSLog(@"Connection failed! Error - %@ %@",
[error localizedDescription],
[[error userInfo] objectForKey:NSURLErrorFailingURLStringErrorKey]);
}
- (IBAction)async:(id)sender
{
[self getJson];
}
@end
</cnt;>
NSRunLoop を利用して通信待ちを行っています。ちょっとしたやりとりならこれで十分だと思います。
connection:シリーズは毎回同じなのでひとまとめにしておくとあとで使い回しするときに便利です。
他のがっつりした取り方は次回解説します。
序盤すぎて覚えてませんが、たぶん参考にしたのはここだと思います
■バリューアークコンサルティング株式会社
NSURLConnectionとNSJSONSerializationを使用してjsonをパースする
■YoheiM.NET
[XCODE] iPhoneアプリからHTTPリクエストを送信して、データを受信する方法