![]() ![]() ![]() ![]() iPhoneアプリケーションでXML Webサービスを利用するこの記事のURLhttp://japan.internet.com/developer/20100108/26.html
著者:Wei-Meng Lee
海外internet.com発の記事
はじめに外部の世界との通信は、iPhoneアプリケーションを楽しく便利なものにする方法の1つです。これだけ多くの便利な機能を提供する無数のWebサービスが存在する今日では、特にそう言えます。しかしiPhoneでWebサービスを利用することは、なかなか根性が必要な作業です。他の開発ツール(Microsoft Visual Studioなど)と異なり、XcodeにはWebサービスの利用を容易にするツールが組み込まれていません。すべてを手作業で行い、Webサービスに送信する適切なXMLメッセージを作成し、返されたXMLの結果を解析する方法を知る必要があります。本稿では、iPhoneアプリケーション内からXML Webサービスと通信する方法を詳しく説明します。本稿のサンプルを通じて、各プロジェクトから他のWebサービスを利用するための基礎を学んでもらいたいと思います。 Webサービスを利用するWebサービスを利用するXcodeプロジェクトにとりかかる前に、実際のWebサービスを使って、どのような方法が存在するかを見てみましょう。筆者が気に入っている例は、.NETで作成したASMX XML Webサービスを使用するものです。ここでは具体的な例として、IPアドレスを入力するとそのIPアドレスが所属する国を返す、IPToCountryというWebサービスを使います。IPToCountryは、http://www.ecubicle.net/iptocountry.asmxにあります。SafariでこのURLをロードすると、このWebサービスが、図1に示す2つのWebメソッドを公開していることが分かります。 例えば FindCountryAsXMLは、結果(国)をXML文字列として返します。図1の[FindCountryAsXML]のリンクをクリックすると、図2のページが表示されます。重要なのは、このページの「Test」セクションに続くセクションです。そこには、このWebサービスを利用するためのさまざまな方法が詳しく示されています。.NETの世界では、Webサービスへのアクセスはかなり単純明快な処理です。Visual Studioには、WSDLドキュメントをダウンロードしたときに、Webサービス用のWeb Proxyサービスオブジェクトを自動的に作成するツールが組み込まれているからです。iPhone開発では、これを手作業で行わなければなりません。従って、Webサービスの利用の根底にある仕組みを理解することが、非常に重要となります。 SOAP 1.1を使用するこのWebサービスを利用するための方法の1つは、SOAP(Simple Object Access Protocol)を使用することです。SOAPを使用する場合は、次の例に示すように、HTTPPOSTメソッドを用いてWebサービスにリクエストを送信します。
POST /iptocountry.asmx HTTP/1.1
Host: www.ecubicle.net
Content-Type: text/xml; charset=utf-8
Content-Length: length
SOAPAction: "http://www.ecubicle.net/webservices/FindCountryAsXml"
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<FindCountryAsXml xmlns="http://www.ecubicle.net/webservices/">
<V4IPAddress>string</V4IPAddress>
</FindCountryAsXml>
</soap:Body>
</soap:Envelope>
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<FindCountryAsXml xmlns="http://www.ecubicle.net/webservices/">
<V4IPAddress>string</V4IPAddress>
</FindCountryAsXml>
</soap:Body>
</soap:Envelope>
HTTP/1.1 200 OK
Content-Type: text/xml; charset=utf-8
Content-Length: length
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<FindCountryAsXmlResponse
xmlns="http://www.ecubicle.net/webservices/">
<FindCountryAsXmlResult>xml result</FindCountryAsXmlResult>
</FindCountryAsXmlResponse>
</soap:Body>
</soap:Envelope>
SOAP 1.2を使用するSOAP 1.2の使い方は、SOAP 1.1の使い方に非常によく似ています。リクエストの例を次に示します。
POST /iptocountry.asmx HTTP/1.1
Host: www.ecubicle.net
Content-Type: application/soap+xml; charset=utf-8
Content-Length: length
<?xml version="1.0" encoding="utf-8"?>
<soap12:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:soap12="http://www.w3.org/2003/05/soap-envelope">
<soap12:Body>
<FindCountryAsXml xmlns="http://www.ecubicle.net/webservices/">
<V4IPAddress>string</V4IPAddress>
</FindCountryAsXml>
</soap12:Body>
</soap12:Envelope>
HTTP/1.1 200 OK
Content-Type: application/soap+xml; charset=utf-8
Content-Length: length
<?xml version="1.0" encoding="utf-8"?>
<soap12:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:soap12="http://www.w3.org/2003/05/soap-envelope">
<soap12:Body>
<FindCountryAsXmlResponse
xmlns="http://www.ecubicle.net/webservices/">
<FindCountryAsXmlResult>xml result</FindCountryAsXmlResult>
</FindCountryAsXmlResponse>
</soap12:Body>
</soap12:Envelope>
HTTP GETを使用する多くのWebサービスでは、何らかの理由でSOAPを使用したくない場合には、それよりも簡単なHTTPGETメソッドを使用できます。GETリクエストを送信するための形式を次に示します。GET /iptocountry.asmx/FindCountryAsXml?V4IPAddress=string HTTP/1.1 Host: www.ecubicle.net HTTP/1.1 200 OK Content-Type: text/xml; charset=utf-8 Content-Length: length
<?xml version="1.0"?> xml result HTTP POSTを使用するHTTPGETを使用する方法に加えて、HTTP POSTを使用する方法もあります。リクエストを送信する形式を次に示します。POST /iptocountry.asmx/FindCountryAsXml HTTP/1.1 Host: www.ecubicle.net Content-Type: application/x-www-form-urlencoded Content-Length: length V4IPAddress=string
HTTP/1.1 200 OK Content-Type: text/xml; charset=utf-8 Content-Length: length <?xml version="1.0"?> xml result iPhoneアプリケーションでWebサービスを利用する背景をすべて説明したところで、いよいよお待ちかねのiPhoneアプリケーションでWebサービスを利用するという作業にとりかかります。まず、SOAPを使用してWebサービスと通信する方法を説明します。Xcodeを用いて、ViewベースのApplicationプロジェクトを作成し、これに「WebServices」という名前を付けます。ファイル「WebServicesViewController.xib」をダブルクリックして、Interface Builderの中で開きます。[View]アイテムをダブルクリックし、次の各ビューを配置します。図3は、これらのビューを示したものです。
#import <UIKit/UIKit.h>
@interface WebServicesViewController : UIViewController {
//---outlets---
IBOutlet UITextField *ipAddress;
IBOutlet UIActivityIndicatorView *activityIndicator;
//---web service access---
NSMutableData *webData;
NSMutableString *soapResults;
NSURLConnection *conn;
}
@property (nonatomic, retain) UITextField *ipAddress;
@property (nonatomic, retain) UIActivityIndicatorView *activityIndicator;
- (IBAction)buttonClicked:(id)sender;
@end
図4 接続の完了: この図には、[File's Owner]アイテムのアウトレット接続とアクションが示されている
![]() import "WebServicesViewController.h" @implementation WebServicesViewController @synthesize ipAddress; @synthesize activityIndicator; buttonClicked:メソッド(リスト1参照)を定義します。リスト1 ボタンクリック時のコード
- (IBAction)buttonClicked:(id)sender {
NSString *soapMsg =
[NSString stringWithFormat:
@"<?xml version=\"1.0\" encoding=\"utf-8\"?>"
"<soap:Envelope
xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"
xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\"
xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\">"
"<soap:Body>"
"<FindCountryAsXml
xmlns=\"http://www.ecubicle.net/webservices/\">"
"<V4IPAddress>%@</V4IPAddress>"
"</FindCountryAsXml>"
"</soap:Body>"
"</soap:Envelope>",
ipAddress.text
];
//---print it to the Debugger Console for verification---
NSLog(soapMsg);
NSURL *url = [NSURL URLWithString:
@"http://www.ecubicle.net/iptocountry.asmx"];
NSMutableURLRequest *req = [NSMutableURLRequest requestWithURL:url];
//---set the headers---
NSString *msgLength = [NSString stringWithFormat:@"%d",
[soapMsg length]];
[req addValue:@"text/xml; charset=utf-8"
forHTTPHeaderField:@"Content-Type"];
[req addValue:@"http://www.ecubicle.net/webservices/FindCountryAsXml"
forHTTPHeaderField:@"SOAPAction"];
[req addValue:msgLength forHTTPHeaderField:@"Content-Length"];
//---set the HTTP method and body---
[req setHTTPMethod:@"POST"];
[req setHTTPBody: [soapMsg dataUsingEncoding:NSUTF8StringEncoding]];
[activityIndicator startAnimating];
conn = [[NSURLConnection alloc] initWithRequest:req delegate:self];
if (conn) {
webData = [[NSMutableData data] retain];
}
}
NSString *soapMsg =
[NSString stringWithFormat:
@"<?xml version=\"1.0\" encoding=\"utf-8\"?>"
"<soap:Envelope
xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"
xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\"
xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\">"
"<soap:Body>"
"<FindCountryAsXml
xmlns=\"http://www.ecubicle.net/webservices/\">"
"<V4IPAddress>%@</V4IPAddress>"
"</FindCountryAsXml>"
"</soap:Body>"
"</soap:Envelope>", ipAddress.text
];
NSURL *url = [NSURL URLWithString: @"http://www.ecubicle.net/iptocountry.asmx"]; NSMutableURLRequest *req = [NSMutableURLRequest requestWithURL:url];
次に、
Content-Type、SOAPAction、Content-Lengthなどの各種ヘッダを、リクエストオブジェクトに設定しています。HTTPメソッドとHTTPボディも設定しています。NSString *msgLength = [NSString stringWithFormat:@"%d", [soapMsg length]]; [req addValue:@"text/xml; charset=utf-8" forHTTPHeaderField:@"Content-Type"]; [req addValue:@"http://www.ecubicle.net/webservices/FindCountryAsXml" forHTTPHeaderField:@"SOAPAction"]; [req addValue:msgLength forHTTPHeaderField:@"Content-Length"]; [req setHTTPMethod:@"POST"]; [req setHTTPBody: [soapMsg dataUsingEncoding:NSUTF8StringEncoding]]; [activityIndicator startAnimating];
conn = [[NSURLConnection alloc] initWithRequest:req delegate:self];
if (conn) {
webData = [[NSMutableData data] retain];
}
dataメソッドは、バイトバッファのラッパーとなる、空のデータオブジェクトを返します。これを用いてWebサービスからのデータを受信します。Webサービスからのデータの受信が開始すると、次に示す connection:didReceiveResponse:メソッドが呼び出されます。
-(void) connection:(NSURLConnection *) connection
didReceiveResponse:(NSURLResponse *) response {
[webData setLength: 0];
}
webDataの長さを0に初期化していることに注意してください。Webサービスから連続的にデータを受信する場合は、connection:didReceiveData:メソッドが繰り返し呼び出されます。このメソッドを用いて、受信したデータをwebDataオブジェクトに付加していきます。
-(void) connection:(NSURLConnection *) connection
didReceiveData:(NSData *) data {
[webData appendData:data];
}
connection:didFailWithError:メソッドが呼び出されます。
-(void) connection:(NSURLConnection *) connection
didFailWithError:(NSError *) error {
[webData release];
[connection release];
}
connectionDidFinishLoading:メソッドが呼び出されます。
-(void) connectionDidFinishLoading:(NSURLConnection *) connection {
NSLog(@"DONE. Received Bytes: %d", [webData length]);
NSString *theXML = [[NSString alloc]
initWithBytes: [webData mutableBytes]
length:[webData length]
encoding:NSUTF8StringEncoding];
//---shows the XML---
NSLog(theXML);
[theXML release];
[activityIndicator stopAnimating];
[connection release];
[webData release];
}
最後に、次の deallocメソッドで、すべてのプロパティとオブジェクトを解放します。
- (void)dealloc {
[ipAddress release];
[activityIndicator release];
[xmlParser release];
[soapResults release];
[super dealloc];
}
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<FindCountryAsXml xmlns="http://www.ecubicle.net/webservices/">
<V4IPAddress>34.5.6.7</V4IPAddress>
</FindCountryAsXml>
</soap:Body>
</soap:Envelope>
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope
xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<soap:Body>
<FindCountryAsXmlResponse
xmlns="http://www.ecubicle.net/webservices/">
<FindCountryAsXmlResult>
<IPCountryService xmlns="">
<Country>United States</Country>
</IPCountryService>
</FindCountryAsXmlResult>
</FindCountryAsXmlResponse>
</soap:Body>
</soap:Envelope>
<Country>要素の中にあります。本稿の最後のセクションでは、XMLレスポンスを解析する方法を説明しますが、その前に、SOAPメソッドを用いてWebサービスと通信する代わりに、HTTP GETとHTTP POSTメソッドを使用したい場合にどうすればよいかを紹介します。Webサービスに対してHTTP POSTを使用するHTTPPOSTを使用してWebサービスと通信するために必要な作業は、次のようにbuttonClicked:メソッドを変更することだけです。
- (IBAction)buttonClicked:(id)sender {
NSString *postString =
[NSString stringWithFormat:@"V4IPAddress=%@",
ipAddress.text];
NSLog(postString);
NSURL *url = [NSURL URLWithString:
@"http:// www.ecubicle.net/iptocountry.asmx/FindCountryAsXml"];
NSMutableURLRequest *req = [NSMutableURLRequest requestWithURL:url];
NSString *msgLength =
[NSString stringWithFormat:@"%d", [postString length]];
[req addValue:@"application/x-www-form-urlencoded"
forHTTPHeaderField:@"Content-Type"];
[req addValue:msgLength forHTTPHeaderField:@"Content-Length"];
[req setHTTPMethod:@"POST"];
[req setHTTPBody: [postString
dataUsingEncoding:NSUTF8StringEncoding]];
[activityIndicator startAnimating];
conn = [[NSURLConnection alloc] initWithRequest:req delegate:self];
if (conn) {
webData = [[NSMutableData data] retain];
}
}
V4IPAddress=34.5.6.7のような単一の文字列をWebサービスにPOSTするだけとなります。変更したアプリケーションを実行し、Debugger Consoleを見ると、次のようなレスポンスが返されているはずです。
<?xml version="1.0" encoding="utf-8"?>
<IPCountryService>
<Country>United States</Country>
</IPCountryService>
Webサービスに対してHTTP GETを使用するHTTPGETを用いたWebサービスとの通信は、さらに容易です。すべてのリクエスト情報がクエリ文字列で引き渡されるからです。HTTP GETメソッドを用いてWebサービスにアクセスするように変更したbuttonClicked:メソッドを次に示します。
- (IBAction)buttonClicked:(id)sender {
NSString *queryString =
[NSString stringWithFormat:
@"http://www.ecubicle.net/iptocountry.asmx/" +
"FindCountryAsXml?V4IPAddress=%@",
ipAddress.text];
NSURL *url = [NSURL URLWithString:queryString];
NSMutableURLRequest *req = [NSMutableURLRequest requestWithURL:url];
[req addValue:@"text/xml; charset=utf-8"
forHTTPHeaderField:@"Content-Type"];
[req addValue:0 forHTTPHeaderField:@"Content-Length"];
[req setHTTPMethod:@"GET"];
[activityIndicator startAnimating];
conn = [[NSURLConnection alloc] initWithRequest:req delegate:self];
if (conn) {
webData = [[NSMutableData data] retain];
}
}
POSTメソッドの場合と同様に、変更したアプリケーションを実行したときにDebugger Consoleに表示されるレスポンスは次のようになります。
<?xml version="1.0" encoding="utf-8"?>
<IPCountryService>
<Country>United States</Country>
</IPCountryService>
XMLレスポンスを解析するさて、Webサービスを呼び出すためのさまざまの方法を理解したところで、次にXMLレスポンスを解析する方法について説明します。iPhone SDKには、XMLファイルを解析するために使用できるNSXMLParserオブジェクトがあります。NSXMLParserクラスは、XMLドキュメント(または文字列)を逐次的に解析するSimple API for XML(SAX)を実装したものです。NSXMLParserオブジェクトは、XMLドキュメントを読み込み、その最初から最後までをスキャンします。ドキュメントの中で各種項目(要素、属性、コメントなど)を検出するたびに、要素の値の抽出など、適切なアクションが実行できるように、デリゲートに通知します。 Webサービスからのレスポンスを解析するには、ファイル「WebServicesViewController.h」に次のステートメントを追加します。
#import <UIKit/UIKit.h>
@interface WebServicesViewController : UIViewController {
//---outlets---
IBOutlet UITextField *ipAddress;
IBOutlet UIActivityIndicatorView *activityIndicator;
//---web service access---
NSMutableData *webData;
NSMutableString *soapResults;
NSURLConnection *conn;
//---xml parsing---
NSXMLParser *xmlParser;
BOOL *elementFound;
}
@property (nonatomic, retain) UITextField *ipAddress;
@property (nonatomic, retain) UIActivityIndicatorView *activityIndicator;
- (IBAction)buttonClicked:(id)sender;
@end
connectionDidFinishLoading:メソッドに、次の太字で示したステートメントを追加します。
-(void) connectionDidFinishLoading:(NSURLConnection *) connection {
NSLog(@"DONE. Received Bytes: %d", [webData length]);
NSString *theXML = [[NSString alloc]
initWithBytes: [webData mutableBytes]
length:[webData length]
encoding:NSUTF8StringEncoding];
//---shows the XML---
NSLog(theXML);
[theXML release];
[activityIndicator stopAnimating];
if (xmlParser)
{
[xmlParser release];
}
xmlParser = [[NSXMLParser alloc] initWithData: webData];
[xmlParser setDelegate: self];
[xmlParser setShouldResolveExternalEntities:YES];
[xmlParser parse];
[connection release];
[webData release];
}
最初に実装するメソッドは、 parser:didStartElement:namespaceURI:qualifiedName:attributes:です。このメソッドは要素の開始タグを検出したときに呼び出されます。
//---when the start of an element is found---
-(void) parser:(NSXMLParser *) parser
didStartElement:(NSString *) elementName
namespaceURI:(NSString *) namespaceURI
qualifiedName:(NSString *) qName
attributes:(NSDictionary *) attributeDict {
if( [elementName isEqualToString:@"Country"])
{
if (!soapResults)
{
soapResults = [[NSMutableString alloc] init];
}
elementFound = YES;
}
}
<Country>であるかどうかを調べています。そうである場合、Boolean型の変数elementFoundにYESを設定します。次に実装するメソッドは parser:foundCharacters:です。パーサーが要素のテキストを検出すると呼び出されます。
-(void)parser:(NSXMLParser *) parser foundCharacters:(NSString *)string
{
if (elementFound)
{
[soapResults appendString: string];
}
}
<Country>ならば、Country要素の値を取り出し、soapResultsに代入する処理を行います。最後は、パーサーが要素の終わりを検出した場合に呼び出す parser:didEndElement:namespaceURI:qualifiedName:メソッドです。
//---when the end of element is found---
-(void)parser:(NSXMLParser *)parser
didEndElement:(NSString *)elementName
namespaceURI:(NSString *)namespaceURI
qualifiedName:(NSString *)qName
{
if ([elementName isEqualToString:@"Country"])
{
//---displays the country---
NSLog(soapResults);
UIAlertView *alert = [[UIAlertView alloc]
initWithTitle:@"Country found!"
message:soapResults
delegate:self
cancelButtonTitle:@"OK"
otherButtonTitles:nil];
[alert show];
[alert release];
[soapResults setString:@""];
elementFound = FALSE;
}
}
</Country>タグを探しています。これを検出すれば、Country要素の値が正しく抽出されたことになります。次に、UIAlertViewオブジェクトを用いてその値をプリントします。これで完成です。ここでCommand-Rキーを押して、このアプリケーションをiPhone Simulatorでテストできます。IPアドレスを入力し、[Find Country]ボタンをクリックします。図5に、その結果を示します。 以上、実際のiPhoneアプリケーションを使って、SOAP、HTTP GET、HTTP POSTという3種類の方法でiPhoneアプリケーションからWebサービスを利用する例を紹介しました。今回の例では、Webサービスを呼び出し、XML形式のレスポンスからデータを抽出する方法を示しました。Webサービスの利用(およびXMLの解析)方法を習得すれば、Webの世界にあふれているさまざまなデータをiPhoneアプリケーションで利用できるようになります。著者紹介Wei-Meng Lee(Wei-Meng Lee)
Microsoft MVP受賞者。Microsoft社の最新テクノロジー実地研修を専門とするDeveloper Learning Solutions社を創設。.NETとワイヤレステクノロジーの開発者、指導者として知られる。
国際的なカンファレンスでたびたび講演し、.NET、XML、ワイヤレステクノロジーに関する著書、共著書多数。.NETからMac OS Xに至るまで広範な分野について執筆している。著書に『.NET Compact Framework Pocket Guide』 (Oreilly&Associates Inc、2005年5月)、『ASP.NET 2.0: A Developer's Notebook』 (Oreilly&Associates Inc、2005年6月)、『Programming Sudoku』 (Apress刊、2006年3月)など。ブログ「Wei-Meng Lee's Blog」を開設している。
japan.internet.comのウエブサイトの内容は全て、国際法、日本国内法の定める著作権法並びに商標法の規定によって保護されており、その知的財産権、著作権、商標の所有者はインターネットコム株式会社、インターネットコム株式会社の関連会社または第三者にあたる権利者となっています。
本サイトの全てのコンテンツ、テキスト、グラフィック、写真、表、グラフ、音声、動画などに関して、その一部または全部を、japan.internet.comの許諾なしに、変更、複製、再出版、アップロード、掲示、転送、配布、さらには、社内LAN、メーリングリストなどにおいて共有することはできません。 ただし、コンテンツの著作権又は所有権情報を変更あるいは削除せず、利用者自身の個人的かつ非商業的な利用目的に限ってのみ、本サイトのコンテンツをプリント、ダウンロードすることは認められています。 |