iOSアプリでXMLを解析する方法(NSXMLParser)【性能向上編】

前回はNSXMLParserを使用して、XMLデータを解析する方法を紹介した。
ただ、前回紹介した方法は、性能面で問題点がある。今回はその問題点に対する改善策を紹介する。

<前回の問題点>
下記の実行結果からも分かるように、取得したいcountの値は4行目で取得できているにも関わらず、その後もXMLデータの解析を継続している。

実行結果(再掲)

2014-11-10 22:48:07.170 XmlSample[16477:110619] 解析開始
2014-11-10 22:48:07.171 XmlSample[16477:110619] 要素の開始タグを読み込んだ:users
2014-11-10 22:48:07.171 XmlSample[16477:110619] 要素の開始タグを読み込んだ:count
2014-11-10 22:48:07.172 XmlSample[16477:110619] タグ以外のテキストを読み込んだ:2
2014-11-10 22:48:07.172 XmlSample[16477:110619] 要素の終了タグを読み込んだ:count
2014-11-10 22:48:07.172 XmlSample[16477:110619] 要素の開始タグを読み込んだ:timestamp
2014-11-10 22:48:07.172 XmlSample[16477:110619] タグ以外のテキストを読み込んだ:2014/11/10 19:20:21.123
2014-11-10 22:48:07.173 XmlSample[16477:110619] 要素の終了タグを読み込んだ:timestamp
2014-11-10 22:48:07.173 XmlSample[16477:110619] 要素の開始タグを読み込んだ:user
2014-11-10 22:48:07.173 XmlSample[16477:110619] 要素の開始タグを読み込んだ:name
2014-11-10 22:48:07.173 XmlSample[16477:110619] タグ以外のテキストを読み込んだ:Taro
2014-11-10 22:48:07.173 XmlSample[16477:110619] 要素の終了タグを読み込んだ:name
2014-11-10 22:48:07.173 XmlSample[16477:110619] 要素の終了タグを読み込んだ:user
2014-11-10 22:48:07.174 XmlSample[16477:110619] 要素の開始タグを読み込んだ:user
2014-11-10 22:48:07.176 XmlSample[16477:110619] 要素の開始タグを読み込んだ:name
2014-11-10 22:48:07.176 XmlSample[16477:110619] タグ以外のテキストを読み込んだ:Hanako
2014-11-10 22:48:07.176 XmlSample[16477:110619] 要素の終了タグを読み込んだ:name
2014-11-10 22:48:07.177 XmlSample[16477:110619] 要素の終了タグを読み込んだ:user
2014-11-10 22:48:07.177 XmlSample[16477:110619] 要素の終了タグを読み込んだ:users
2014-11-10 22:48:07.177 XmlSample[16477:110619] 解析終了
2014-11-10 22:48:07.177 XmlSample[16477:110619] 解析結果 countの値:2

<あるべき姿>
取得したいcountの値が取得できた時点で、XMLデータの解析処理を中断し結果を表示する。

実行結果(あるべき姿のイメージ)

2014-11-10 22:48:07.170 XmlSample[16477:110619] 解析開始
2014-11-10 22:48:07.171 XmlSample[16477:110619] 要素の開始タグを読み込んだ:users
2014-11-10 22:48:07.171 XmlSample[16477:110619] 要素の開始タグを読み込んだ:count
2014-11-10 22:48:07.172 XmlSample[16477:110619] タグ以外のテキストを読み込んだ:2
2014-11-10 22:48:07.172 XmlSample[16477:110619] 要素の終了タグを読み込んだ:count
2014-11-10 22:48:07.177 XmlSample[16477:110619] 解析結果 countの値:2

<改善策>
取得したいcountの値を取得した時点で、「NSXMLParser#abortParsing」メソッドを呼び出し、XMLデータの解析処理を中断する。
ただし、「NSXMLParser#abortParsing」を呼び出すと、エラー発生時に呼び出されるデリゲートメソッド「parser: parseErrorOccurred:」がエラーコード「NSXMLParserDelegateAbortedParseError」で呼び出されるので、このメソッドも実装してやる必要がある。

<ソースコード>

#import "XmlParse.h"

@implementation XmlParse

NSString *result;

BOOL isTarget;

-(void) doParse{
    
    //解析対象となるXMLを作成
    NSString *strXml = @"<users><count>2</count><timestamp>2014/11/10 19:20:21.123</timestamp><user><name>Taro</name></user><user><name>Hanako</name></user></users>";
    
    //NSData型に変換
    NSData *dataXml = [strXml dataUsingEncoding:NSUTF8StringEncoding];
    
    //NSXMLParser初期化
    NSXMLParser *parser = [[NSXMLParser alloc] initWithData:dataXml];
    
    //デリゲートの設定
    parser.delegate = self;
    
    //解析開始
    [parser parse];
}

//デリゲートメソッド(解析開始時)
-(void) parserDidStartDocument:(NSXMLParser *)parser{

    NSLog(@"解析開始");
    
    //解析の初期化処理
    isTarget = NO;
}

//デリゲートメソッド(要素の開始タグを読み込んだ時)
- (void) parser:(NSXMLParser *)parser
didStartElement:(NSString *)elementName
   namespaceURI:(NSString *)namespaceURI
  qualifiedName:(NSString *)qName
     attributes:(NSDictionary *)attributeDict{
    
     NSLog(@"要素の開始タグを読み込んだ:%@",elementName);
    
    if([elementName isEqualToString:@"count"]){
        
        isTarget = YES;     //データを取得するターゲットである事を保持する
    }
    
}

//デリゲートメソッド(タグ以外のテキストを読み込んだ時)
- (void) parser:(NSXMLParser *)parser foundCharacters:(NSString *)string{
    
    NSLog(@"タグ以外のテキストを読み込んだ:%@", string);
    
    if(isTarget){
        result = string;
    }
}

//デリゲートメソッド(要素の終了タグを読み込んだ時)
- (void) parser:(NSXMLParser *)parser
  didEndElement:(NSString *)elementName
   namespaceURI:(NSString *)namespaceURI
  qualifiedName:(NSString *)qName{
    
    NSLog(@"要素の終了タグを読み込んだ:%@",elementName);
    
    if(isTarget){
        
        isTarget = NO;
        
        [parser abortParsing];      //←★☆★ ポイント:解析処理を中断する ★☆★
    }
}

//デリゲートメソッド(解析終了時)
-(void) parserDidEndDocument:(NSXMLParser *)parser{
    
    NSLog(@"解析終了");
    
    [self dispResult];
}

//デリゲートメソッド(エラー発生時)
-(void) parser:(NSXMLParser *)parser parseErrorOccurred:(NSError *)parseError{  //メソッドを追加
    
    if([parseError code] == NSXMLParserDelegateAbortedParseError){
        
        NSLog(@"取得したい値が取得できたのでXMLデータの解析を中断");
        
        [self dispResult];
        
    }else{
        
        NSLog(@"予期せぬエラーが発生");
    }
}

//結果の表示
-(void) dispResult{
    
    NSLog(@"解析結果 countの値:%@",result);
}

@end

<実行結果>

2014-11-10 22:53:00.100 XmlSample[16769:113690] 解析開始
2014-11-10 22:53:00.101 XmlSample[16769:113690] 要素の開始タグを読み込んだ:users
2014-11-10 22:53:00.101 XmlSample[16769:113690] 要素の開始タグを読み込んだ:count
2014-11-10 22:53:00.101 XmlSample[16769:113690] タグ以外のテキストを読み込んだ:2
2014-11-10 22:53:00.102 XmlSample[16769:113690] 要素の終了タグを読み込んだ:count
2014-11-10 22:53:00.102 XmlSample[16769:113690] 取得したい値が取得できたのでXMLデータの解析を中断
2014-11-10 22:53:00.102 XmlSample[16769:113690] 解析結果 countの値:2

ほぼ、あるべき姿のイメージと一致した結果を得る事ができた。

今回使用した短いXMLデータでは、処理時間に大きな差異は無い。
ただ、数MByteのXMLデータの序盤にある値を取得するケースでは、ユーザが体感できるほどの差異が発生すると思われる。

Enjoy Programing!!

<関連記事>
iOSアプリでXMLを解析する方法(NSXMLParser)【基礎編】
iOSアプリでXMLを解析する方法(NSXMLParser)【性能向上編】(本記事)

<お勧め書籍>

詳解 Objective-C 2.0 第3版
iOSアプリ開発技術者として仕事をするのであれば、必ず読んでおくべき書籍である。
筆者も何度も繰り返し精読している。