【コラム】

実践! iPhoneアプリ開発

8 RSSリーダの作り方 (4) - libxmlでパースする

    木下誠  [2009/04/17]

    前回はXMLパースライブラリの検討をして、libxmlを採用する事にした。今回は、実際にlibxmlを組み込んで、パースを行ってみよう。

    libxmlのプロジェクトへの追加

    まずプロジェクトにlibxmlを加える必要がある。

    ここまででプロジェクトに追加されている外部のライブラリは、フレームワーク形式のものだった。UIKit.frameworkや、Foundation.frameworkなどだ。これらのファイルは、SDKフォルダの下の、Sysytem/Library/Frameworksに入れられている。SDKフォルダは、/Developer/Platforms/iPhoneOS.platform/Developer/SDKsにある。

    これに対して、libxmlのようなUNIX由来のライブラリは、別のフォルダの中にある。SDKフォルダの下の、usr/libだ。インクルードファイルは、usr/includeになる。

    この中にlibxmlのためのファイルがある。iPhoneSDK2.2では、libxml2.2.dylibがインストールされている。このファイルへのシンボリックリンクであるlibxml2.dylibが用意されているので、こちらをプロジェクトに追加しよう。

    プロジェクトへの追加は簡単で、libxml2.dylibファイルを、Finderからドラッグ&ドロップで追加してやればいい。または、[プロジェクト]→[プロジェクトに追加…]メニューからファイルを選択する。

    インクルードパスの追加も必要である。こちらは、ターゲットの情報画面から行う。ビルドの設定項目に「ヘッダ検索パス」があるので、ここにlibxml2のためのインクルードパスを追加しよう。SDKフォルダの下の、usr/include/libxml2になる。

    これで、libxmlを使う準備が整った。

    libxmlのSAXハンドラ

    今回は、パフォーマンスとメモリのフットプリントを考慮して、libxmlのSAXパーサを使う。SAXパーサでは、パースをしながらXMLの要素や属性を発見し、それをアプリケーションに通知してくれる。このために、ハンドラを登録しておく必要がある。

    libxmlのSAXハンドラは、xmlSAXHandlerという構造体で定義されている。この構造体では、30ほどのハンドラが定義されている。これらを全部登録する必要はない。アプリケーションの中で、必要なものだけ使えばいいのだ。ここでは、要素の開始、要素の終わり、テキスト、を通知するハンドラを登録しよう。多くの場合、これだけでも十分だろう。

    これらのハンドラは、それぞれstartElementNsSAX2Func、endElementNsSAX2Func、charactersSAXFuncという型になっている。次のような定義になっている。

    List 1.

    typedef void (*startElementNsSAX2Func) (
                    void *ctx,
                    const xmlChar *localname,
                    const xmlChar *prefix,
                    const xmlChar *URI,
                    int nb_namespaces,
                    const xmlChar **namespaces,
                    int nb_attributes,
                    int nb_defaulted,
                    const xmlChar **attributes);
    
    typedef void (*endElementNsSAX2Func) (
                    void *ctx,
                    const xmlChar *localname,
                    const xmlChar *prefix,
                    const xmlChar *URI);
    
    typedef void (*charactersSAXFunc) (
                    void *ctx,
                    const xmlChar *ch,
                    int len);
    

    要素の開始の通知の際には、名前空間なども渡されるのが分かるだろう。

    これらの第一引数に、ctxというものがあるのに注目してほしい。これは、ユーザが任意に指定できるデータとなる。Cocoaアプリでは、ここに、このXMLパーサをハンドリングするオブジェクトのアドレスを入れておくと便利だ。なぜなら、ハンドラ自体はC言語のコールバックなので、C関数となる。この中で処理をするのはなにかと不便なので、オブジェクトを使って、そちらのメソッドに処理を投げてしまうようにすればいい。

    DownloadOperationに組み込む

    では、このlibxmlを組み込んでみよう。前回までにDownloadOperationという、フィードをダウンロードするためのクラスを作成した。これに組み込んでみる。

    まずこのクラスに、SAXパーサのためのインスタンス変数を追加しよう。これは、xmlParserCtxtPtrという型になる。

    List 2.

    #import <Foundation/Foundation.h>
    #import <libxml/tree.h>
    
    @interface DownloadOperation : NSOperation
    {
        NSURLRequest*       _request;
        NSURLConnection*    _connection;
        xmlParserCtxtPtr    _parserContext;
        BOOL                _isExecuting, _isFinished;
    }
    
    // Initialize
    - (id)initWithRequest:(NSURLRequest*)request;
    
    @end
    

    まず、#import文でlibxml/tree.hヘッダファイルを読み込んでおく。そして、xmlParserCtxtPtr型の変数を宣言する。前回まであった、ダウンロードしたデータを保存しておくための_data変数は、ダウンロード中に逐次パースを行うため、必要なくなったので削除した。

    次に、SAXハンドラを紹介しよう。

    List 3.

    static void startElementHandler(
            void* ctx, 
            const xmlChar* localname, 
            const xmlChar* prefix, 
            const xmlChar* URI, 
            int nb_namespaces, 
            const xmlChar** namespaces, 
            int nb_attributes, 
            int nb_defaulted, 
            const xmlChar** attributes)
    {
        [(DownloadOperation*)ctx 
                startElementLocalName:localname 
                prefix:prefix URI:URI 
                nb_namespaces:nb_namespaces 
                namespaces:namespaces 
                nb_attributes:nb_attributes 
                nb_defaulted:nb_defaulted 
                attributes:attributes];
    }
    

    このように、ハンドラが呼び出された直後にDownloadOperationのメソッドを呼び出して、処理をそちらに渡すようにしている。

    フィードのダウンロードを始めるときは、先にパーサを作成する。パーサの作る関数はいくつかあるが、ここではダウンロドーしたデータを逐次追加できるようにしたいので、xmlCreatePushParserCtxtを使おう。

    List 4.

    - (void)start
    {
        // ダウンロードを開始する
        if (![self isCancelled]) {
            // XMLパーサを作成する
            _parserContext = xmlCreatePushParserCtxt(&_saxHandlerStruct, self, NULL, 0, NULL);
            ...
    

    ダウンロードが始まりデータが得られたら、順次データを追加していく。これには、xmlParseChunkを使えばいい。

    List 5.

    - (void)connection:(NSURLConnection*)connection
            didReceiveData:(NSData*)data
    {
        // データを追加する
        xmlParseChunk(_parserContext, (const char*)[data bytes], [data length], 0);
    }
    

    これがlibxmlを組み込んだDownloadOperationクラスだ。NSXMLParserを使うものと比べて、高いパフォーマンスと、少ないメモリフットプリントを実現できているはずだ。

    次回は、RSSデータを取り出して、アプリケーションを仕上げていこう。

    ここまでのソースコード: RSS-2.zip

    関連したタグ

    新着記事

    特設サイトの情報

      求人情報

      人気記事

      一覧

      イチオシ記事

      新着記事

      特別企画

      転職ノウハウ

      あなたの仕事適性診断

      4つの診断で、自分の適性を見つめなおそう!

      Heroes File ~挑戦者たち~

      働くこと・挑戦し続けることへの思いを綴ったインタビュー

      はじめての転職診断

      あなたにピッタリのアドバイスを読むことができます。

      転職Q&A

      転職に必要な情報が収集できます

      スカウト転職する

      企業からアプローチのメッセージが届きます。

      マイナビニュースマガジン