はりまや日記

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

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

以下略

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

以下次号

iphoneアプリ開発日記その4

過去に作ったアプリに関しての備忘録的な何か

発端

iphone4s で使っていた google talk 用のチャットツールが、不安定な動作をするようになった。
もともと多少不満もあったので、ios 開発の練習がてらにアプリを作ってみようかと。

不満としては

  • 他のアプリで送受信した履歴を表示する
  • ひたすら電話帳をインポートしようとして、起動が遅い

やったこと

とりあえず以下のサイトを参考に xmpp チャットアプリを動かしてみた。

setup/Building a Jabber Client for iOS: Interface Setup

サンプルコードは ARS 対応ではないので修正。
チャットビューから戻るとメッセージが無くなるので、AppDelegate に持たせるように修正。
というか、まず受信した段階でデータを保存しておいて、必要に応じて表示すると。
また、バディリスト表示時にメッセージ受信すると死ぬので、そのへんも修正してみた。
あと アカウント名を表示したりとか。

ただ、メインの目的である履歴の取得方法がわからない。

今後やるとすれば、 アカウントの画像を表示できるようにするとかか。
ejabberd の設定方法がよくわからなかったので、そのへんを調べてみるのもいいかも。


終焉

とりあえず google 公式のハングアウトアプリを使うと、全履歴が表示できるようだ。

ということは、自作アプリからも履歴を取得できるのかも。

iphoneアプリ開発日記その3

Block とラムダとクロージャの間

ios の Blocks について調べてみた


ひとまず前買ってた本を読んでみる

これー> 詳解 Objective-C 2.0 第3版


…ああ、これラムダなのかな?

そういえば、どこかのサイトに Blocks = クロージャ みたいに書いてあった気がする。
クロージャについて調べてみよう

http://ja.wikipedia.org/wiki/%E3%82%AF%E3%83%AD%E3%83%BC%E3%82%B8%E3%83%A3

どうやら、クロージャはラムダ式を使って実現しているとのこと。「ラムダ式 ⊃ Blocks」 って感じか

で、実行時でなく定義時の環境をコピーするんだとか


試してみた

ソース
        int captured_value = 10;
        __block int block_value = 20;
        
        // Blocks 用の変数を宣言
        void (^external_block)(int);
        void (^external_declared_block)(int);
        
        
        // 実体を代入
        external_block = ^(int loop){
            printf("external          ~ loop: %d, captured: %d, block: %d\n", loop, captured_value, block_value);
            block_value ++;

        };
        
        for (int loop_counter = 0; loop_counter < 3; loop_counter++){

            external_block(loop_counter);
            external_declared_block = ^(int loop){
                printf("external declared ~ loop: %d, captured: %d\n", loop, captured_value);
            };
            external_declared_block(loop_counter);
            void (^internal_block)(int) = ^(int loop){
                printf("internal          ~ loop: %d, captured: %d, block: %d\n\n", loop, captured_value, block_value);
            };
            internal_block(loop_counter);
            captured_value ++;
            
        }

        external_block(111);
        external_declared_block(111); //スコープ外なんで未定義のはず。やっちゃだめ
結果
external          ~ loop: 0, captured: 10, block: 20
external declared ~ loop: 0, captured: 10
internal          ~ loop: 0, captured: 10, block: 21

external          ~ loop: 1, captured: 10, block: 21
external declared ~ loop: 1, captured: 11
internal          ~ loop: 1, captured: 11, block: 22

external          ~ loop: 2, captured: 10, block: 22
external declared ~ loop: 2, captured: 12
internal          ~ loop: 2, captured: 12, block: 23

external          ~ loop: 111, captured: 10, block: 23
external declared ~ loop: 111, captured: 12

ということで、宣言時の変数が保持されているのがわかる
__block ってやると、Blocks 内部で変数を弄れるようになる



注意点

Delegate の代わりに Blocks を使うのが流行ってるらしいのだが、
Blocks 内部でメンバ変数にアクセスすると、さっくり循環参照を起こすとか。

対応策はこんな感じのようだ

  • 弱参照をつかう
  • 解放処理をきっちり書く

いまいち完全に把握できてないので、もう少し調べる必要があるかなー

iphoneアプリ開発日記その2

さくっと現状をまとめてみる。

 

ゴール設定

設定するゴールは、ひとまずこんな感じ

  • ios 関連の職に就く
  • apple store にアプリを出す
  • なんらかのゲームを作る

 

で、それと平行して objective-c の学習を行います。

できてること

  • クリップボード機能の利用
  • pickerView の利用
  • 基本的な UI の実装(テーブルとか)
  • xmpp を使ったチャットツール作成(google talk 用)
  • NSUserDefault を使ったアプリデータ保存
  • AppDelegate を自作して差し替え
  • バックグランド実行(10分間のやつ)
  • cocos2d の基本的な利用
  • Delegate 関連の理解

やること予定

  • 通知センター関連
  • xcode でのテストコードの作り方
  • ソフトキーボードを閉じる処理をいろいろ実装してみる
  • Blocks の学習
  • 課金関連の処理実装
  • iAd 実装
  • ios でのネットワーク関連処理(ソケットかその上のレイヤー?)
  • Unity4でゲーム作成
  • cocos 2d でまともなゲーム作成

 

できればやりたい

  • GCD 学習
  • UITableViewCell のカスタム
  • テーブルのソート機能実装
  • NAS 接続する画像ビューア作成
  • サムネイル機能をもった動画プレイヤーの作成
  • タブ機能を持つRSSリーダー的なアプリ作成
  • イベントで動くクリップボード監視機能(今はポーリングなので)

 

iphoneアプリ開発日記その1

あらすじ

  • はりまや は ていきてきに おこる 
  • チャレンジングな しょうどう に かられ
  • アイフォーン へと むかった
  •  
  • だが そこで まちうけていたのは
  • けんとうの おいのり であった
  •  
  • アピールが たりない と いわれ
  • つい かっとなって しまう はりまや
  •  
  • しかし どうじに
  • ひさしぶりに モチベーションが 
  • あがってくるのを かんじているの だった
  •  
  • ついでに げーむでも つくるかと
  • かるい きもちで かんがえている が
  • まだ そのゆくてに まちうけるものを しらない・・・

 

おかしい

iphoneアプリ開発のはずが、

css3 アニメーションとか jquery ばっかりかいてる…

 

web系はあんまやってないので、

よい勉強になりました

 

イメージ→
FF2 op - YouTube