「アプリの考え方」を他のサービスやアプリとつながるiOSアプリをSwiftで作成しながら習得できる本連載。2回にわたって、ネットワーク上のデータをアプリに読み込んで扱う方法について解説しています。前回は「データをアプリにダウンロードする方法」を同期、非同期2つの方法で解説しました。今回は「ダウンロードしたデータをアプリで使える形に解析する方法」について解説します。
解析が必要なデータの形式で代表的なXMLとJSONで解説します。
※本連載では、2015年4月時点で最新のXcode 6.3、Swift 1.2で解説します。
おさえておきたい「XML」と「JSON」
XMLとJSON。名前は聞いたことがあるかもしれませんが、どんなものか具体的にわからない方のために、簡単に解説します。
1)XMLとは
XMLとは、「Extensible Markup Language」の略で、テキストデータの意味や構造を記述するための「テキストフォーマット」です。データはタグ<>を使って記述します。タグを入れ子にして、ツリー構造でデータを扱えます。
例)
<?xml version="1.0" encoding="UTF-8"?>
<data>
<tea name="ダージリン" price="600"/>
<tea name="アールグレイ" price="550"/>
</data>
「data」要素に2つの「tea」要素がある2階層のデータ構造です。tea要素には属性データ「name」と「price」があります。
SwiftでXMLを解析する
Swiftは、「データの構造を考えながら」XMLデータを解析します。先頭から解析し、「<data>」タグで、dataの階層に入ったと判断します。次に「<tea>」タグで、dataの階層の下にteaの階層があると判断し、次の「<tea>」タグで、teaの階層には2つの要素があると判断します。「</data>」タグでdataの階層が終わったと判断します。
2)JSONとは
JSONとは、「JavaScript Object Notation」の略で、JavaScriptのオブジェクトデータを扱う「テキストフォーマット」です。
JSONは、JavaScriptの書き方で記述されている、XMLよりシンプルで扱いやすいデータ形式です。JavaScriptだけでなく、多くのプログラミング言語で自動的に読み込んで処理できます。
例)
{"data":
[
{"name":"ダージリン","price":600},
{"name":"アールグレイ","price":550}
]
}
「data」階層の中に配列があり、配列として複数の要素が入っている3階層の構造で、3階層目の要素の中に「name」と「price」という2つの属性があります。
Swiftは、JSONデータを自動的に読み込んで処理できます。
XMLとJSONを扱えるアプリをSwiftで作る
データ形式がわかったところで実際にネット上にあるデータをアプリにダウンロードして、データを表示するアプリを作成しましょう。XMLやJSONはデータサイズが大きくなりやすいので、ダウンロード待ちがない非同期ダウンロードを使います。
1. XMLを解析する方法
XMLの解析にはSwiftの命令「NSXMLParser」を使います。ダウンロードしたテキストデータを先頭から順番に読み進めて、階層データを含むタグを判断します。
XMLをSwiftで使えるデータとして読み込むには、XMLのデータ構造に応じた解析プログラムが必要です。XMLが複雑になるほど、解析プログラムも複雑になるデメリットがあります。
1)Xcodeのメニュー「File」→「New」→「Project」から「Single View Application」を選択して、新規プロジェクトを作成しましょう。
2)Webにダウンロード用のXMLデータをアップします。データ構造にあわせてプログラミングする必要があるので、今回は以下のデータを使います。
<?xml version="1.0" encoding="UTF-8"?>
<data>
<tea name="ダージリン" price="600"/>
<tea name="アールグレイ" price="550"/>
</data>
※サンプルファイル http://editors.ascii.jp/c-minamoto/swift/swift-5-data.xml で代用できます。
3)画面に[Button]と[textView]を配置して、アプリ画面を作ります。
「Main.storyboard」を選択し、右下のライブラリから[Button]と[textView]をドラッグ&ドロップして配置します。 右下の「|△|」ボタンを押して、「Reset to Suggested Constraints(自動レイアウト設定)」を選択します。
「Main.storyboard」の仮画面の中央にボタンを配置しただけでは、縦長のiPhone画面で見るとボタンが端に表示されてしまいます。自動レイアウト設定をすることで、縦長の画面でも中央に表示されます。
4)[textView]に名前をつけてコントロールします。
メニュー「View」→「Assitant Editor」→「Show Assistant Editor」を選択して、「アシスタントエディター」を表示し、キーボードのcontrolを押しながら、[textView]をViewController.swiftへドラッグして、名前を「myTextView」とします。
ViewControll.swiftにコード、
@IBOutlet weak var myTextView: UITextView!
が追加されます。
5)ボタンが押されたときに実行される関数を作ります。
ボタンが押されたときの命令を記述するための関数を作ります。
キーボードのcontrolを押しながら「Button」をViewController.swiftへドラッグし、「Connection」を「Action」に切り換えてボタンを押したとき実行する関数を作ります。関数名は「tapBtn」にします。
6)ViewController.swiftに、プログラムを記述します。
ボタンが押されたときにサーバーからデータをダウンロードして解析し、アプリに表示するプログラムを記述します。
テキストデータのURLを用意して、19行目「sendAsynchronousRequest」で非同期ダウンロードを実行します。データの読み込みが終わったら、22行目「dispXML関数」が呼び出され、33行目「parser!.parse()」でXML解析を開始します。
XMLの解析を開始したら、タグ情報が見つかるたびにparser関数が自動的に呼ばれます。
開始タグが見つかったら、42行目「parser関数 didStartElement」が呼び出されるので、タグがdataならば、data階層に入ったことがわかるようにXMLtag1変数に「data」という文字列を格納して、判断できるようにします。
タグteaが見つかったときに、判断用に保存していたXMLtag1を見てdataの階層に入っていることがわかったら、tea要素から属性を取りだして「表示用変数 msg」に追加します。
終了タグが見つかったら、60行目「parser関数 didEndElement」が呼び出され、解析したデータを「myTextView」に表示します。
また、XMLデータの読み込みを確認するために、読み込みが完了したときに呼ばれるdispXML関数の中で、26行目「println文」を使ってデバッグエリアへXMLデータを出力します。
記述したプログラムは以下の通りです。
//(中略)
class ViewController: UIViewController, NSXMLParserDelegate{
@IBAction func tapBtn(sender: AnyObject) {
let url:NSURL = NSURL(string: "http://editors.ascii.jp/c-minamoto/swift/swift-5-data.xml")!
let request = NSURLRequest(URL: url)
// 読み込みが終わったらdispXML関数を呼ぶ
NSURLConnection.sendAsynchronousRequest(request, queue: .mainQueue(), completionHandler: self.dispXML)
}
// 読み込みが終わったら行う関数
func dispXML(response:NSURLResponse?, data:NSData?, error:NSError?) {
if data != nil {
// チェックのため、読み込んだデータをそのまま表示
let myString = NSString(data:data!, encoding: NSUTF8StringEncoding) as! String
println(myString)
// XMLの解析を開始
let parser:NSXMLParser? = NSXMLParser(data: data!)
if parser != nil {
// 解析が進んだら、parser関数を呼び出すようにする
parser!.delegate = self;
parser!.parse()
}
}
}
// 1階層目のタグを覚えておく変数を用意
var XMLtag1: String! = ""
// 表示するメッセージを入れる変数
var msg:String = ""
// タグの最初が見つかったら呼ばれる関数
func parser(parser: NSXMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [NSObject : AnyObject]) {
// タグが「data」なら「dataタグ」に入ったことをXMLtag1に覚えておく
if elementName == "data" {
XMLtag1 = "data"
}
// タグが「tea」で、すでに「dataタグ」に入った状態なら、その中のデータを取り出す
if elementName == "tea" && XMLtag1 == "data" {
// 属性「name」の文字を取りだしてmsg変数に追加
if let teaName = attributeDict["name"] as? String {
msg += "名前=\(teaName)\n"
}
// 属性「price」の文字を取りだしてmsg変数に追加
if let teaPrice = attributeDict["price"] as? String {
msg += "価格=\(teaPrice)\n"
}
}
}
// タグの終わりが見つかったら呼ばれる関数
func parser(parser: NSXMLParser, didEndElement elementName: String, namespaceURI: String?, qualifiedName qName: String?) {
// タグが「data」なら タグが閉じられたので、XMLtag1をリセットして、メッセージを表示
if elementName == "data" {
XMLtag1 = ""
myTextView.text = msg
}
}
//(中略)
ボタンを押すと、データが読み込まれて解析され、「textView」に表示されます。
Xcodeの下に表示されているデバッグエリアを確認してみましょう。XMLデータが読み込まれていることがわかります。