はりまや日記

なんか適当にいろいろ綴ったりなんかするところ

iphoneアプリ開発日記その5

RSS リーダーを作る

突然 RSS リーダーが作りたくなった。

参考になりそうなページがあったので、とりあえず見てみる。

【コラム】実践! iPhoneアプリ開発 (5) RSSリーダの作り方 (1) - RSSフィードをダウンロードする | 開発・SE | マイナビニュース
【iPhone開発】RSSを読み込む方法 | STUDIO BEATNIX

おお、動く動く
…がいまいち挙動がわからん。
コード見てもさっぱりさっぱり。

こういうときは、問題を切り分けて個別にやっつけていこう。

RSS リーダーの処理を考えると、こんな感じになるか

  1. xml ダウンロード
  2. xml パース
  3. リスト表示
  4. 選択された要素のページを表示する

それに加えて、「ダウンロード → パース」を並列化している。
なのでその辺も理解する必要がある。


xml パーサーの動きをみる


まずは xml パーサから。
選択肢はこの5つ

名前 解析タイプ 備考
NSXMLParser SAX ios標準
libxml2 SAX ios標準、c言語API
GDataXML DOM google
KissXML DOM
TBXML DOM

性能評価はこのへんHow To Choose the Best XML Parser for your iPhone Project
そのうち読みたい



以前 pyhton で sax パーサを作った気がするので、今回も sax で。
標準的な NSXMLParer を使うことにしよう。

最低限以下のメソッドを実装すれば動くようだ

- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict
- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string
- (void)parser:(NSXMLParser *)parser parseErrorOccurred:(NSError *)parseError


ひとまずこれでログを吐きまくると、挙動のイメージがつきそうだ。

さて、ここで何のタグを解析すればいいのか、ということに思い当たる。
RSSxml ってどういう構成になってるのだろうか。


RSS フィードの構成を調べる


RSS のフィード構成がどうなっているのかを調べてみた。
このへんを参考に⬇
RSSのフォーマット・仕様・構造 - RSS1.0、RSS2.0、Content-Type
RSS(RDF Site Summary)によるサイト情報の要約と公開

大きな要素として、channel と item があって、その中にいろいろ情報が入ってる
入ってる情報の中で、使いそうなものはこのへんか

  • title
  • link
  • description
  • pubDate

なお、date に関しては、標準の pubDate を使っていたり、拡張モジュールの dc:date を使っていたりする。
そのため、一応両対応しておこう。

xmlを解析してみる

さて、RSS フィードをばらしてみよう

作ったサンプルコード

XMLParser.h

#import <Foundation/Foundation.h>

@interface XMLParser : NSObject<NSXMLParserDelegate>{
    NSXMLParser* parser;
    NSMutableDictionary* feedsDict;
    BOOL isChannel;
    BOOL isItem;
}

- (void)parseRSS: (NSURL*) url;

@end

XMLParser.m

#import "XMLParser.h"

@implementation XMLParser

static NSString* CHANNEL_KEY = @"channel";
static NSString* ITEM_KEY = @"item";

- (void)parseRSS: (NSURL*)url{
    
    parser = [NSXMLParser.new initWithContentsOfURL:url];
    
    parser.delegate = self;
    
    [parser parse];
    
}

- (void)parserDidStartDocument:(NSXMLParser *)parser{
    feedsDict = NSMutableDictionary.new;
    isChannel = NO;
    isItem = NO;
}

- (void)parserDidEndDocument:(NSXMLParser *)parser{
    
    isChannel = NO;
    isItem = NO;
    
}

- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict{
    
    if ([elementName isEqualToString:CHANNEL_KEY]){
        NSLog(@"channel %@ start", elementName);
        isChannel = YES;
        
    }else if ([elementName isEqualToString:ITEM_KEY]){
        NSLog(@"item %@ start", elementName);
        isItem = YES;
    } else{
        NSString* log_prefix = @"other";
        if(isChannel){
            log_prefix = CHANNEL_KEY;
        }else if (isItem){
            log_prefix = ITEM_KEY;
        }
        NSLog(@"%@ element: %@ : start", log_prefix, elementName);
    }
    
}

- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName{
    
    if ([elementName isEqualToString:CHANNEL_KEY]){
        NSLog(@"channel %@ end", elementName);
        isChannel = NO;
        
    }else if ([elementName isEqualToString:ITEM_KEY]){
        NSLog(@"item %@ end", elementName);
        isItem = NO;
        
    }else{
        NSString* log_prefix = @"other";
        if(isChannel){
            log_prefix = CHANNEL_KEY;
        }else if (isItem){
            log_prefix = ITEM_KEY;
        }
        NSLog(@"%@ element: %@ : end", log_prefix, elementName);
    }
    
}

- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string{
    NSString* trimmedString = [string stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
    
    if (![trimmedString isEqualToString:@""]) {
        NSLog(@"found: %@", string);
    }
}

- (void)parser:(NSXMLParser *)parser parseErrorOccurred:(NSError *)parseError{
    NSLog(@"error: %@", parseError);
    
}
@end

結果(channel)

2013-06-06 19:52:16.885 CUILesson[82227:303] other element: rdf:RDF : start
2013-06-06 19:52:16.887 CUILesson[82227:303] channel channel start
2013-06-06 19:52:16.887 CUILesson[82227:303] channel element: title : start
2013-06-06 19:52:16.887 CUILesson[82227:303] found: 4Gamer.net 
2013-06-06 19:52:16.888 CUILesson[82227:303] found: − 最新記事
2013-06-06 19:52:16.888 CUILesson[82227:303] channel element: title : end
2013-06-06 19:52:16.888 CUILesson[82227:303] channel element: link : start
2013-06-06 19:52:16.888 CUILesson[82227:303] found: http://www.4gamer.net
2013-06-06 19:52:16.888 CUILesson[82227:303] channel element: link : end
2013-06-06 19:52:16.889 CUILesson[82227:303] channel element: description : start
2013-06-06 19:52:16.889 CUILesson[82227:303] found: 日本最大級の総合ゲームサイトです。コンシューマゲームやオンラインゲーム,iPhone/iPadのゲーム,そしてPC のディープなハードウェア情報まで,日本だけでなく世界からも最新ゲームニュースをお届けします。ほかにも,ユ
2013-06-06 19:52:16.889 CUILesson[82227:303] found: ーザーレビューなどのクチコミ情報も充実。
2013-06-06 19:52:16.889 CUILesson[82227:303] channel element: description : end
2013-06-06 19:52:16.889 CUILesson[82227:303] channel element: dc:language : start
2013-06-06 19:52:16.890 CUILesson[82227:303] found: ja
2013-06-06 19:52:16.890 CUILesson[82227:303] channel element: dc:language : end
2013-06-06 19:52:16.890 CUILesson[82227:303] channel element: dc:rights : start
2013-06-06 19:52:16.890 CUILesson[82227:303] found: Copyright (C) 2000-2013 Aetas Inc. All Rights Reserved. 
2013-06-06 19:52:16.890 CUILesson[82227:303] found: 本サ
イト「4Gamer.net」の内容は,すべて無断転載を禁止します。ただし商用利用を
除き,リンクについてはその限りではありません。転載,商用利用,二次利用の
希望,ご意見などはwebmaster\@4gamer.netまで。
2013-06-06 19:52:16.890 CUILesson[82227:303] channel element: dc:rights : end
2013-06-06 19:52:16.891 CUILesson[82227:303] channel element: dc:date : start
2013-06-06 19:52:16.891 CUILesson[82227:303] channel element: dc:date : end
2013-06-06 19:52:16.891 CUILesson[82227:303] channel element: dc:publisher : start
2013-06-06 19:52:16.891 CUILesson[82227:303] found: Aetas
2013-06-06 19:52:16.891 CUILesson[82227:303] found: (株)

以下略

実行結果ログ(item)

2013-06-06 19:52:17.022 CUILesson[82227:303] item item start
2013-06-06 19:52:17.023 CUILesson[82227:303] item element: title : start
2013-06-06 19:52:17.023 CUILesson[82227:303] found: 「EVE Online」,探索コンテンツの再活性化を目指した大型アップデート「Odyssey」が実装に
2013-06-06 19:52:17.023 CUILesson[82227:303] item element: title : end
2013-06-06 19:52:17.023 CUILesson[82227:303] item element: link : start
2013-06-06 19:52:17.024 CUILesson[82227:303] found: http://www.4gamer.net/games/004/G000412/20130606085/
2013-06-06 19:52:17.024 CUILesson[82227:303] item element: link : end
2013-06-06 19:52:17.024 CUILesson[82227:303] item element: dc:date : start
2013-06-06 19:52:17.024 CUILesson[82227:303] found: 2013-06-06T19:48:55+09:00
2013-06-06 19:52:17.024 CUILesson[82227:303] item element: dc:date : end
2013-06-06 19:52:17.024 CUILesson[82227:303] item element: description : start
2013-06-06 19:52:17.025 CUILesson[82227:303] found:  CCPGamesがサービスを行うMMORPG\343\200\214EVEOnline」に,19回めとなる大型アップデート「Odyssey」実装されたことが,本日(2013年6月6日)ネクソンから発表された。探索コンテンツ再活性化を目指した今回のアップデート。新機能として
2013-06-06 19:52:17.026 CUILesson[82227:303] found: ,星間に隠された宝物や遺物といったものを発見するスキャナ「センサーオーバーレイ」が導入されるなどしている。
2013-06-06 19:52:17.026 CUILesson[82227:303] item element: description : end
2013-06-06 19:52:17.026 CUILesson[82227:303] item item end
2013-06-06 19:52:17.027 CUILesson[82227:303] item item start
2013-06-06 19:52:17.027 CUILesson[82227:303] item element: title : start
2013-06-06 19:52:17.027 CUILesson[82227:303] found: 「『真・女神転生IV」,DLC第3弾が配信。公式サイトにて詳細が公開
2013-06-06 19:52:17.027 CUILesson[82227:303] item element: title : end
2013-06-06 19:52:17.027 CUILesson[82227:303] item element: link : start
2013-06-06 19:52:17.027 CUILesson[82227:303] found: http://www.4gamer.net/games/166/G016690/20130606086/
2013-06-06 19:52:17.028 CUILesson[82227:303] item element: link : end
2013-06-06 19:52:17.028 CUILesson[82227:303] item element: dc:date : start
2013-06-06 19:52:17.028 CUILesson[82227:303] found: 2013-06-06T19:39:13+09:00
2013-06-06 19:52:17.030 CUILesson[82227:303] item element: dc:date : end
2013-06-06 19:52:17.030 CUILesson[82227:303] item element: description : start
2013-06-06 19:52:17.030 CUILesson[82227:303] item element: description : end
2013-06-06 19:52:17.030 CUILesson[82227:303] item item end
2013-06-06 19:52:17.030 CUILesson[82227:303] item item start

以下略

ログを見ていくと、パーサーが開始/終了タグを見ているっぽいのがわかる。
この仕組みを利用して、必要なデータを確保することにしよう

以下次号