Developer's Blog

iOS で XML をパースする

iOS で使える XML パーサーは色々あります。
iOS 標準で使えるものは以下の2点

  • Cocoa Touch フレームワークで提供されている NSXMLParser
  • C言語のライブラリである libxml2 を直接利用

外部のライブラリでは主に以下のようなものがあります。

XML パーサーの中でも SAX と呼ばれる少し扱いにくいが比較的高速で省メモリなもの。DOM と呼ばれる扱いやすいがメモリを多く消費しやすいものがあります。

今回は Cocoa Touch フレームワークで提供されている NSXMLParser の使い方を紹介します。

NSXMLParser は SAX タイプのパーサーで C言語のライブラリである libxml2 をラップしたクラスになります。XML パースのおおまかな流れは、インスタンスを初期化 → デリゲートを設定 → パース(※デリゲートメソッド処理) → インスタンスを解放となります。

パース処理はデリゲートから呼ばれるメソッドで行われます。メソッドが呼ばれる順番は次の通りです。

  • parserDidStartDocument
  • didStartElement
  • foundCharacters
  • didEndElement
  • parserDidEndDocument

※パース処理の中でエラーが発生した場合は、parseErrorOccurred メソッドが呼ばれます。

では、実際にコードを見て行きましょう。
今回パースする XML はこちらです。

<xml>
<element attribute="attribute value">element value</element>
</xml>

以下、XML タグの中身を次のように呼びます。
<要素名 属性=”属性の値”>要素の値</要素名>

まず、パース処理を行うクラスのヘッダを実装します。

#import <Foundation/Foundation.h>
@interface AllDetailXMLParser : NSObject < NSXMLParserDelegate > {
   BOOL isElement;
	NSMutableString * elementBuffer;
	NSMutableArray * detailCaptions;
}
- (void)parseXml:(NSData *)data;
@end

次にパース処理を行うクラスのメソッドを実装します。
今回は、呼び出し元から、NSData 型で XML の内容を引数に渡します。

#import "FenrirXMLParser.h"
@implementation FenrirXMLParser
- (void)parseFenrirXML:(NSData *)data {
	// 小技2:自動開放プールを作成
	NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
	{
		// インスタンスを初期化
		NSXMLParser * xmlParser = [[NSXMLParser alloc] initWithData:data];
		// デリゲートを設定
		[xmlParser setDelegate:self];
		// 小技1:パース開始時間を取得
		NSDate *xmlParseTime = [NSDate date];
		// パース
		[xmlParser parse];
		// 小技1:パース終了時間を出力
		NSLog(@"Xml parse time is %lf (sec)", [[NSDate date] timeIntervalSinceDate:xmlParseTime]);
		// 小技1:パース時間を解放
		[xmlParseTime release]; xmlParseTime = nil;
		// インスタンスを解放
		[xmlParser release]; xmlParser = nil;
	}
	// 小技2:自動開放プールを解放
	[pool release]; pool = nil;
}

// XMLのパース開始
- (void)parserDidStartDocument:(NSXMLParser *)parser {
	// element フラグを初期化(NO に設定)
	isElement = NO;
}

// 要素の開始タグを読み込み
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qualifiedName attributes:(NSDictionary *)attributeDict {
	// element があるかどうかチェック
   if ([elementName isEqualToString:@"element"]) {
		// element フラグを YES に設定
		isElement = YES;
		// 要素の値を入れる領域を初期化する
		elementBuffer = [[NSString alloc] init];
		// attribute 属性があるかどうかチェックする
		if ([attributeDict objectForKey:@"attribute"]) {
			// attribute 属性の値をコピーする
			attributeBuffer = [attributeDict objectForKey:@"attribute"];
		}
	}
}

// 要素の値を読み込み
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string {
	// element フラグが YES かどうかチェック
	if (isElement) {
		// 要素の値を elementBuffer へ追加
		[elementBuffer appendString:string];
	}
}

// 要素の閉じタグを読み込み
- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName {
	// element フラグが YES かどうかチェック
	if (isElement) {
		// element フラグを NO に設定
		isElement = NO;
	}
}

// XML のパース終了
- (void)parserDidEndDocument:(NSXMLParser *)parser {
	if (elementBuffer) {
		NSLog(@"elementBuffer: %@", elementBuffer);
	}
	if (attributeBuffer) {
		NSLog(@"attributeBuffer: %@", attributeBuffer);
	}
}

// エラー
- (void)parser:(NSXMLParser *)parser parseErrorOccurred:(NSError *)parseError {
	// エラーの内容を出力
	NSLog(@"Error: %i, Column: %i, Line: %i, Description: %@",
		[parseError code],
		[parser columnNumber],
		[parser lineNumber],
		[parseError description]);
}
@end

今回は小技を2つプログラムの中に実装しました。
小技1:パース時間を出力
NSXMLParser の parse メソッドの前後に NSDate の timeIntervalSinceDate メソッドを実装し、パース時間を出力します。
小技2:自動開放プールを設定
パース処理を NSAutoreleasePool クラスのインスタンスで挟むことにより、パース処理で使われたメモリを綺麗に解放します。

最後に、エラーが出てしまった時の対応ですが、iOS Developer Library の NSXMLParser Class Reference → Parser Error Constants を参照するとエラー内容を見ることができます。

参考 URL
How To Choose The Best XML Parser for Your iPhone Project | Ray Wenderlich

Copyright © 2019 Fenrir Inc. All rights reserved.