前回はRSSフィードをダウンロードするところまで説明した。今回は、ダウンロードしたXMLデータをパースすることについて、説明しよう。

iPhoneのXMLライブラリ

iPhoneは、標準で2種類のXMLをパースするためのライブラリを備える。libxml2とNSXMLParserだ。

libxml2はGnomeプロジェクトで開発された、XMLのパーサライブラリだ。もともとは、GnomeというLinuxディストリビューションの1つに対して開発されたものだが、MITライセンスのもとでソースコードが公開されているため、多くのプラットフォームに移植されている。もちろんMac OS Xにも移植され、それがiPhoneでも使えるようになっている。

libxml2の特徴は、高機能である事だ。まず基本的なXMLパーサ機能として、SAXおよびDOMパーサを提供する。さらに、XMLに関する多くの標準に対応している。たとえば、XML、名前空間、XPath、XPointer、HTML、XInclude、XSLT、XML Schema、Relax NGなどが挙げられる。また、C言語で書かれているため、高速に動作する。APIもC言語で提供される。

NSXMLParserは、Cocoaに含まれるXMLパースのためのクラスだ。SAXパーサ機能を提供する。Cocoaの一部のため、APIはObjective-Cで提供される。そのため、Cocoaアプリケーションとの親和性は非常によい。使いこなすのは簡単だろう。

XMLパースとメモリのフットプリント

さて、このようなXMLライブラリがあることは分かったが、どれを使うべきだろう?ここで考慮しなくてはいけないのは、iPhoneはやはり組み込みデバイスの一種であるということだ。つまり、メモリやCPUといったハードウェアリソースは限られている。ならば、ライブラリの選定を行うときは、パフォーマンスとメモリのフットプリントに気をつけなくてはいけない。

まず一般に、SAXパーサとDOMパーサのメモリのフットプリントを比べると、SAXの方が小さくなる。これは、もともとのパーサの設計思想の違いによるものだ。流れ処理的にパースを行うSAXと、木構造を保持することを目的とするDOMでは、メモリの使用量はもちろん差が出る。

では、iPhoneで実際にDOMパーサを使う事を考えてみる。現在のiPhone 3Gは、搭載されている物理メモリは128MB程度。そのうちiPhone OSが使用するものがあり、ユーザの使用状況によっては音楽を再生していたり、メールやSafariが常駐していたりする。ならば、自分のアプリケーションが使えるメモリ領域は、良くて10MB程度と見積もっておいた方が無難だろう。

そのような状況でXMLのパースを行うのだが、昨今はインターネット上でやりとりされるXMLファイルは肥大化の傾向にある。たとえばRSSは、もともとはWebページの要約を配信するものだった。それがRSSリーダの発展に伴って、記事の全文を入れたり、テキスト形式とHTML形式の両方を加えているものも珍しくない。これにより、数百KBや数MBのRSSフィードも見かけるようになった。

これほどの大きさのものを、DOMでパースするのは、避けておいた方がいいだろう。あっという間にアプリケーションのメモリ領域を食いつぶして、クラッシュしてしまうことが予想される。対象となるXMLファイルが十分に小さい事が保証されるのであれば、問題はない。だがRSSリーダのように、どのようなサイズのファイルが来るのか分からない場合は、DOMパーサの使用は避けたい。

libxml2 vs. NSXMLParser

ならば、libxml2のSAXパーサか、NSXMLParserか、ということになる。実は、この2つを比較するためのサンプルが、iPhone SDKに付属している。XMLPerformanceというサンプルだ。

このサンプルでは、iTunes Storeからトップ300のRSSをダウンロードして、パースする。その際に、使用するXMLライブラリを、libxml2かNSXMLParserかで指定する事ができ、そのパフォーマンスを計測してくれるものだ。

筆者の環境で何回かテストしてみたところ、次のような結果を得た。

ダウンロード パース 合計
NSXMLParser 1.419s 5.525s 7.134s
libxml2 2.520s 2.247s 2.646s

libxml2の方が、NSXMLParserよりも高速な事が分かるだろう。正直、圧倒的とも言える。アプリケーションを使用するときに、十分体感できる差だ。

この差はどこから来るのだろうか。NSXMLParserは、パースを行うバックエンドとしてlibxml2を使っている。従って、パース機能の差ではないだろう。

まず考えられるのが、Objective-C APIによるものだ。NSXMLParserでは、SAX APIの呼び出し時に、引数の文字列を渡すためにNSStringオブジェクトやNSDictionaryオブジェクトを作成している。すべてのタグに対してこの生成を行うのだから、時間のロスは決して小さくない。

もう1つ考えられるのが、パース処理をどの時点から開始するのか、ということだ。先ほどの表をもう一度見てほしい。NSXMLParserの方は合計時間が、ほぼダウンロード時間とパース時間とを足したものになっているのに対して、libxml2の方はそうはなっていない。これは、NSXMLParserはすべてのXMLデータをダウンロードしてからパースを開始するのに対して、libxml2では一部のデータが得られたらすぐにパースを開始しているのだ。そのため、ダウンロード時間と合計時間が、だいたい同じくらいになっている。ユーザにとっても、逐次的に結果が得られるので、心理的負担が軽いだろう。

このような理由で、大きいXMLファイルをパースするときは、libxml2の使用をお勧めする。次回は、libxml2を使ったパースの実際を紹介しよう。