[iOS][Web]iPhoneアプリケーションとサーバ間の通信(その3) | Cocoa練習帳

[iOS][Web]iPhoneアプリケーションとサーバ間の通信(その3)

おそらく、Twitterフレームワークの為だと思うが、iOS5からNSJSONSerializationというJSONを扱うクラスが追加された。


このクラスは非常にシンプルで、用意されているメソッドは以下の5つだけだ。

+ JSONObjectWithData:options:error:
JSONデータを配列(NSArray)/辞書(NSDictionary)に変換して返す。
+ JSONObjectWithStream:options:error:error
ストリームから得られたJSONデータを配列(NSArray)/辞書(NSDictionary)に変換して返す。
+ dataWithJSONObject:options:error:
配列(NSArray)/辞書(NSDictionary)をJSONデータに変換して返す。
+ writeJSONObject:toStream:options:error:
配列(NSArray)/辞書(NSDictionary)をJSONデータに変換してストリームに書き込む。
+ isValidJSONObject:
指定されたFondationのidがJSONデータに変換可能か確認する(だと思う)


ようするに、JSONデータとFondationの配列/辞書に相互に変換するメソッド。JSONデータについては、NSDataとストリームの2種類に対応ということだ。




これで、前回のRESTを使ったアプリケーションをJSON対応に変更できるはずだ。ただ、まだ、NSJSONSerializationを使った情報が世の中には少ないのと、railsアプリケーションが返す結果が、著者が想像していたのと事なってので、試行錯誤はあったが。




以下が、試行錯誤したコードだ。




- (IBAction)sendPost:(id)sender
{
    NSURL   *url = [NSURL URLWithString:@"http://localhost:3000/people.json"];
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
    [request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
    [request setHTTPMethod:@"POST"];
    NSMutableDictionary *person = [NSMutableDictionary dictionary];
    [person setValue:@"MURAKAMI Yukio" forKey:@"name"];
    [person setValue:@"18" forKey:@"age"];
    NSError *error = nil;
    NSData  *content = [NSJSONSerialization dataWithJSONObject:person options:NSJSONWritingPrettyPrinted error:&error];
    [request setHTTPBody:content];
    NSHTTPURLResponse   *response = nil;
    NSData  *data = [NSURLConnection sendSynchronousRequest:request
                                          returningResponse:&response
                                                      error:&error];
    content = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:&error];
    if (!error) {
        NSString    *s = [[NSString alloc] initWithFormat:@"status code: %d\ndata: %@", [response statusCode], content];
        self.textView.text = s;
        NSLog(@"%@", self.textView.text);
    }
    else {
        NSString    *s = [[NSString alloc] initWithFormat:@"error: %@", error];
        self.textView.text = s;
        NSLog(@"%@", self.textView.text);
    }
}
 
- (IBAction)sendGet:(id)sender
{
    NSURL   *url = [NSURL URLWithString:@"http://localhost:3000/people/1.json"];
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
    [request setHTTPMethod:@"GET"];
    NSHTTPURLResponse   *response = nil;
    NSError *error = nil;
    NSData  *data = [NSURLConnection sendSynchronousRequest:request
                                          returningResponse:&response
                                                      error:&error];
    NSDictionary    *content = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:&error];
    if (!error) {
        NSString    *s = [[NSString alloc] initWithFormat:@"status code: %d\ndata: %@", [response statusCode], content];
        self.textView.text = s;
        NSLog(@"%@", self.textView.text);
    }
    else {
        NSString    *s = [[NSString alloc] initWithFormat:@"error: %@", error];
        self.textView.text = s;
        NSLog(@"%@", self.textView.text);
    }
}
 
- (IBAction)sendGetList:(id)sender
{
    NSURL   *url = [NSURL URLWithString:@"http://localhost:3000/people.json"];
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
    [request setHTTPMethod:@"GET"];
    NSHTTPURLResponse   *response = nil;
    NSError *error = nil;
    NSData  *data = [NSURLConnection sendSynchronousRequest:request
                                          returningResponse:&response
                                                      error:&error];
    NSDictionary    *content = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:&error];
    if (!error) {
        NSString    *s = [[NSString alloc] initWithFormat:@"status code: %d\ndata: %@", [response statusCode], content];
        self.textView.text = s;
        NSLog(@"%@", self.textView.text);
    }
    else {
        NSString    *s = [[NSString alloc] initWithFormat:@"error: %@", error];
        self.textView.text = s;
        NSLog(@"%@", self.textView.text);
    }
}
 
- (IBAction)sendPut:(id)sender
{
    NSURL   *url = [NSURL URLWithString:@"http://localhost:3000/people/1.json"];
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
    [request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
    [request setHTTPMethod:@"PUT"];
    NSMutableDictionary *person = [NSMutableDictionary dictionary];
    [person setValue:@"MURAKAMI Yukio" forKey:@"name"];
    [person setValue:@"81" forKey:@"age"];
    NSError *error = nil;
    NSData  *content = [NSJSONSerialization dataWithJSONObject:person options:NSJSONWritingPrettyPrinted error:&error];
    [request setHTTPBody:content];
    NSHTTPURLResponse   *response = nil;
    NSData  *data = [NSURLConnection sendSynchronousRequest:request
                                          returningResponse:&response
                                                      error:&error];
    content = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:&error];
    if (!error) {
        NSString    *s = [[NSString alloc] initWithFormat:@"status code: %d\ndata: %@", [response statusCode], content];
        self.textView.text = s;
        NSLog(@"%@", self.textView.text);
    }
    else {
        NSString    *s = [[NSString alloc] initWithFormat:@"error: %@", error];
        self.textView.text = s;
        NSLog(@"%@", self.textView.text);
    }
}
 
- (IBAction)sendDelete:(id)sender
{
    NSURL   *url = [NSURL URLWithString:@"http://localhost:3000/people/1.json"];
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
    [request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
    [request setHTTPMethod:@"DELETE"];
    NSHTTPURLResponse   *response = nil;
    NSError *error = nil;
    NSData  *data = [NSURLConnection sendSynchronousRequest:request
                                          returningResponse:&response
                                                      error:&error];
    NSDictionary    *content = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:&error];
    if (!error) {
        NSString    *s = [[NSString alloc] initWithFormat:@"status code: %d\ndata: %@", [response statusCode], content];
        self.textView.text = s;
        NSLog(@"%@", self.textView.text);
    }
    else {
        NSString    *s = [[NSString alloc] initWithFormat:@"error: %@", error];
        self.textView.text = s;
        NSLog(@"%@", self.textView.text);
    }
}



JSONのContent-Typeは、application/jsonなのですね。RESTの時のapplication/xmlのままにしていたら、railsアプリケーションの方でXMLとして処理をしようとして、エラーになってしまった。


また、成功したら、HTTPステータスは200だと思っていたら、そうではなかった。それは、railsアプリケーションのpeople_controller.rbで以下のように記述しているからだと思う。




  # POST /people
  # POST /people.json
  def create
    @person = Person.new(params[:person])


    respond_to do |format|
      if @person.save
        format.html { redirect_to @person, :notice => 'Person was successfully created.' }
        format.xml { render :xml => @person, :status => :created, :location => @person }
        format.json { render :json => @person, :status => :created, :location => @person }
      else
        format.html { render :action => "new" }
        format.xml { render :xml => @person.errors, :status => :unprocessable_entity }
        format.json { render :json => @person.errors, :status => :unprocessable_entity }
      end
    end
  end



Rubyを知らず、仕様も確認しないで当てずっぽうだが、POSTの場合、:statusを:createdに設定しているのでHTTPステータスは201(created)を返す。




  # PUT /people/1
  # PUT /people/1.json
  def update
    @person = Person.find(params[:id])


    respond_to do |format|
      if @person.update_attributes(params[:person])
        format.html { redirect_to @person, :notice => 'Person was successfully updated.' }
        format.xml { head :no_content }
        format.json { head :no_content }
      else
        format.html { render :action => "edit" }
        format.xml { render :xml => @person.errors, :status => :unprocessable_entity }
        format.json { render :json => @person.errors, :status => :unprocessable_entity }
      end
    end
  end



PUTの場合、:statusを:no_contentに設定しているので、HTTPステータスは204(no content)を返すようだ。何故、エラーとなるのか、少し、驚いてしまった。




ソースコード
GitHubからどうぞ。

https://github.com/murakami/workbook/tree/master/ios/JSON - GitHub


関連情報
JSONの紹介

RFC4627

iOSプログラミング逆引きリファレンス108 ~知りたいことがすぐわかるiPhoneプログラミングテクニック~

NSJSONSerialization Class Reference

Developerサイトの情報。