2010年10月14日木曜日

NSFetechedResultsController は使わなくてもいいけど、

NSFetechedResultsController を使うと、SQLiteのデータ取得とデータ取得後の情報の取り扱いをいい感じにコントロールできるようになる。

らしい。

Objective-C + libxml2 を使ってフィードを取り込みたいんだ(7)

前々回からの続き。

NSURLConnection で非同期でフィードを勝手に読み込んでくれるのは助かるよ。
けど、NSURLConnection で非同期処理をほぼ同時期にいっぱい開くのはどうだろうか。
良くなくなくないってことで対応しようと思う。


ここで、libxml2 のことでなくて NSOperation の話に変わって行くことになるらしい。
気にせず進める。

調べてたらNSOperation使うのは、裏で複数の処理を動かしたいんだったら、行儀よくスレッド分けて動かしたほうがいいんじゃないか。ってことっぽい。



しかし難しい。いろいろと調べてて混乱が深まるばかりだ。
いま混乱しているのは通知のところ。
NSOperationQueueのキューに突っ込んだNSOperation なインスタンスは、メインスレッドからは切り離されて処理されてるけど、処理が終了したときはメインスレッドに「終わったよ」ってお知らせしてね。これが通知。
で、混乱してるのは通知する方法が2つあるっぽいってこと。

1.キー値監視
2.Cocoa Notification

NSOperation のことをあっちこっち見ながら調べてると、キー値監視でやってることが多い。
AppleのサンプルのTopSongsだとNotification使ってる。


どうするか。通知はどっちでやっていくか。
簡単そうなのはキー値監視する方。
Cocoa Notification は茨の道だけど、使えるようになると応用が効きそう。


って、両方やらないとだろ。
ってことで「Objective-C + libxml2 を使ってフィードを取り込みたいんだ」の続きを中断して

・「キー値監視(key-value observing 略してKVO)を調べる」
・「Cocoa Notification を調べる」

を(できれば)やっていきたい。


(そろそろ挫折するかもしれん。)

2010年10月13日水曜日

Objective-C + libxml2 を使ってフィードを取り込みたいんだ (6)

ちょっと休憩。

libxml2のSAXパーサか、NSXMLParserか。

どちらもSAXパーサだけど、ダウンロードサイズが大きめのXMLだったらlibxml2を選んだ方が良いらしい。

NSXMLParserのが実装簡単。

やりたいことはNSXMLParserで十分賄えただろうに。

速いに越したことはないということで。

続く

Objective-C + libxml2 を使ってフィードを取り込みたいんだ (5)

前回の(4)は説明不足すぎるような気がする。気が向いたら内容を見直してみる。

とりあえずフィードを読み込むことはできるようになった。
作ったRSSImporterってクラスでは、指定した1つのフィードURLにアクセスしてブログの情報を持ってこれた。



次は、1つだけじゃなくて複数のフィードURLを指定して情報を取得したいと思う。

RSSImporterクラスでは、「指定した1つのフィードURLにアクセスしてブログの情報を持ってこれる。」
っていう作りは変更しない。
変更はしないけど、フィードURLをメソッド内で直接指定するのではなくて、インスタンス作るときに指定するように改良しよう。

- (id)init を使うのやめて、 -(id)initWithRequest: を作ってこっちで初期化処理をすることにする。

// RSSImporter.h

// これ追加
- (id)initWithRequest:(NSURLRequest *)requestURL;


// Rssimporter.m

//
// これ追加
//
// - (id)init { // これ削除
- (id)initWithRequest:(NSURLRequest *)requestURL {

self = [super init];
if (self != nil) {

self.isBuffering = NO;
self.characterBuffer = [NSMutableData data];

// パーサ用のコンテクストを作る
if (!context) {
context = xmlCreatePushParserCtxt(&simpleSAXHandlerStruct, self, NULL, 0, NULL);
}


// これ削除
//NSString *feedURL = @"http://eyesrobe.blogspot.com/feeds/posts/default?alt=rss";
//NSURLRequest *requestURL = [NSURLRequest requestWithURL:[NSURL URLWithString:feedURL]];

NSURLConnection *urlConnection = [[[NSURLConnection alloc] initWithRequest:requestURL delegate:self] autorelease];
if (urlConnection == nil) {
NSLog(@"error");
}
}

return self;
}

これで、RSSImporterのインスタンスを作るときにURLを指定できるようになった。

こんな感じで。

// {YourProject}AppDelegate.m

@interface FooAppDelegate : NSObject {
UIWindow *window;
FooViewController *viewController;

// これ削除
// RSSImporter *importer;
}


// {YourProject}AppDelegate.h

// これ削除
// @synthesize importer;


- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {

// これを削除
// importer = [[RSSImporter alloc] init];

// これ追加
NSString *feedURL1 = @"http://eyesrobe.blogspot.com/feeds/posts/default?alt=rss";
NSString *feedURL2 = @"http://blog.eyesrobe.com/feed/rss";
NSArray *feeds = [NSArray arrayWithObjects:feedURL1, feedURL2, feedURL1, feedURL2, feedURL1, feedURL2, feedURL1, feedURL2, nil];
for (NSString *feedURL in feeds) {

NSURLRequest *requestURL = [NSURLRequest requestWithURL:[NSURL URLWithString:feedURL]];

RSSImporter *importer;
importer = [[RSSImporter alloc] initWithRequest:requestURL];
[importer autorelease];
}


[window addSubview:viewController.view];
[window makeKeyAndVisible];
return YES;
}


- (void)dealloc {
// これ削除
// [importer release];

[viewController release];
[window release];
[super dealloc];
}


たくさんのURLを指定するとどうなるかを見たかったので無駄にURLを指定してみた。
NSArray *feeds = [NSArray arrayWithObjects:feedURL1, feedURL2, feedURL1, feedURL2, feedURL1, feedURL2, feedURL1, feedURL2, nil];

で、どうなるかというと、
順番に処理が開始されて、同時並行的に情報の読み込みが行われてる感じになっているっぽい。順番に開始するけど順番に終了はしなくて、読み込み終わった順に終了するってことになるっぽい。

それで何か問題があるかどうか?


『RSSリーダでは、複数のフィードを一度にダウンロードすることが求められる。しかし、数十個のNSURLConnectionを一気に作成したら、帯域を食いつぶしてしまうだろう。』
実践! iPhoneアプリ開発「RSSリーダの作り方 (2) - 複数のダウンロードを並行して処理する」から引用


ってことで、続く。

Memoha 1.2.3 と Moneyha 1.0.1

2010年10月7日に Memoha version 1.2.3 が無事にリリースされた。
2010年9月29日に Moneyha version 1.0.1 が無事にリリースされた。

2010年10月5日火曜日

UITableVIew のセルを反転させない

UITableViewCell のプロパティに selectionStyle ってのがあるから UITableViewCellSelectionStyleNone を指定するだけでいいのかな。

cell.selectionStyle = UITableViewCellSelectionStyleNone;

Objective-C + libxml2 を使ってフィードを取り込みたいんだ (4)

Xcode 3.2.4 and iOS SDK 4.1 で試してる。
古いバージョンとか新しいバージョンのこと考えてない。

さて、続きを書こう。少し間があいたので前回は何を書いてたのか忘れた。
軽く見なおしたら「C言語の関数からMethod呼び出せるって不思議」ってことみたいなのでその続きになるっぽい。

今回はそうすると、実際にパースするところを書いていけばいいか。

流れとしてはこんな感じ。

  1. [Objective-C] connection:didReceiveData: でデータを受信(分割して取得される)
  2. [C] startElementSAX でタグの始まりを判断
  3. [C] charactersFoundSAX でタグ間の文字データを取得(タグの終わりまで繰り返される)
  4. [C] endElementSAX でタグの終わりを判断
  5. [C] 2.に戻る
  6. [Objective-C] 1.に戻る
  7. [Objective-C] connectionDidFinishLoading: でデータ受信の完了


「結局どこで文字列を取得できてるんだよ!」ってことが知りたいわけだが、それは4.のタグの終わりを判断するところ。
ここでバッファに貯めたタグ間の文字列を取得するためにゴニョゴニョしてる。


// RSSImporter.h

#import <Foundation/Foundation.h>
#import <libxml/tree.h>

@interface RSSImporter : NSObject {
@private
xmlParserCtxtPtr context;
// ここ追加
BOOL isItem; // 記事情報か
BOOL isBuffering; // バッファー中か
NSMutableData *characterBuffer; // バッファする箱
}

// ここ追加
@property BOOL isItem;
@property BOOL isBuffering;

- (void)fromC;
- (void)fromC:(const char *)localname;

// ここ追加
- (void)appendCharacters:(const char *)characters length:(NSInteger)length;
- (void)finishAppendCharacters:(NSString *)element;

@end



// RSSImporter.m の該当箇所を編集すること

//
// 要素開始の通知を受けたときの処理
//
static void startElementSAX(
void *context,
const xmlChar *localname,
const xmlChar *prefix,
const xmlChar *URI,
int nb_namespaces,
const xmlChar **namespaces,
int nb_attributes, int nb_defaulted,
const xmlChar **atrributes) {
// void *context は Objective-C との橋渡し。
// xmlCreatePushParserCtxt()の第2引数でselfとして渡されたのが、contextとして渡ってきた。
// 型を合わせることで、Objective-Cのオブジェクトとして扱えるようにしている。
RSSImporter *paserImporter = (RSSImporter *)context;

// ここ追加
// タグの開始(要素の開始)を判断する。例えば<title>xxx</title>なら<title>を判断してくれる。
// バッファし始めることをここで宣言(paserImporter.isBuffering=YES)する。
if (paserImporter.isItem == YES) {
if (strncmp((const char*)localname, "title", sizeof("title")) == 0) {
paserImporter.isBuffering = YES;
}
else if (strncmp((const char*)localname, "link", sizeof("link")) == 0) {
paserImporter.isBuffering = YES;
}
else if (strncmp((const char*)localname, "description", sizeof("description")) == 0) {
//paserImporter.isBuffering = YES; // 今回はコメントにして記事本文は読み込まないようにしとく
}
else if (strncmp((const char*)localname, "pubDate", sizeof("pubDate")) == 0) {
paserImporter.isBuffering = YES;
}

} else {
if (strncmp((const char*)localname, "item", sizeof("item")) == 0) {
paserImporter.isItem = YES;
}
else {
paserImporter.isItem = NO;
}
}
}

//
// 要素終了の通知を受けたときの処理
//
static void endElementSAX(
void *context,
const xmlChar *localname,
const xmlChar *prefix,
const xmlChar *URI) {
RSSImporter *paserImporter = (RSSImporter *)context;

// ここ追加
// タグの終了(要素の終了)を判断する。例えば<title>xxx</title>なら</title>を判断してくれる。
// バッファを終了する。
// 例えば<title>あいうえお</title>なら、Objective-C側で溜めた文字列「あいうえお」
// を確定させて次のバッファを溜められるようにしている。
if (paserImporter.isBuffering == YES) {
if (strncmp((const char*)localname, "title", sizeof("title")) == 0) {
[paserImporter finishAppendCharacters:@"title"];
}
else if (strncmp((const char*)localname, "link", sizeof("link")) == 0) {
[paserImporter finishAppendCharacters:@"link"];
}
else if (strncmp((const char*)localname, "description", sizeof("description")) == 0) {
[paserImporter finishAppendCharacters:@"description"];
}
else if (strncmp((const char*)localname, "pubDate", sizeof("pubDate")) == 0) {
[paserImporter finishAppendCharacters:@"pubDate"];
}
}
paserImporter.isBuffering = NO;
}

//
// 要素間のテキストの通知を受けたときの処理
//
static void charactersFoundSAX(
void *context,
const xmlChar *characters,
int length) {
RSSImporter *paserImporter = (RSSImporter *)context;

// ここ追加
// 通知で受け取る文字列は、一回だけじゃなくて連続してくる。(短ければ1回で受け取れる)
// それらをその都度Objective-C側にバッファを溜めていく。
// 例えば<title>あいうえお</title>なら、startElementSAXからendElementSAXの間
// で断続して文字列「あいうえお」を取得することになる。
if (paserImporter.isBuffering == YES) {
[paserImporter appendCharacters:(const char *)characters length:length];
}
}




// RSSImporter.m の該当箇所を編集すること

//
// クラスエクステンションを利用することで、
// プロパティとメソッドへのアクセスをプライベートからに制限できる
//
@interface RSSImporter ()
// これ追加
@property (nonatomic, retain) NSMutableData *characterBuffer;
@end




// RSSImporter.m の該当箇所を編集すること

@implementation RSSImporter

// ここ追加
@synthesize isItem; // 記事情報か
@synthesize isBuffering; // バッファー中か
@synthesize characterBuffer; // バッファする箱

/**
*/
- (id)init {

self = [super init];
if (self != nil) {

// ここ追加
self.isBuffering = NO;
self.characterBuffer = [NSMutableData data];

// パーサ用のコンテクストを作る
if (!context) {
context = xmlCreatePushParserCtxt(&simpleSAXHandlerStruct, self, NULL, 0, NULL);
}

NSString *feedURL = @"http://eyesrobe.blogspot.com/feeds/posts/default?alt=rss";
NSURLRequest *requestURL = [NSURLRequest requestWithURL:[NSURL URLWithString:feedURL]];
NSURLConnection *urlConnection = [[[NSURLConnection alloc] initWithRequest:requestURL delegate:self] autorelease];
if (urlConnection == nil) {
NSLog(@"error");
}
}

return self;
}

/**
ここ追加
パースされて送られてきた文字を、バッファに溜める。
NSMutableDataなcharacterBufferに送られてきた文字をがんがん入れていく。
Cの関数から呼ばれる不思議
*/
- (void)appendCharacters:(const char *)characters length:(NSInteger)length {
[characterBuffer appendBytes:characters length:length];
}

/**
ここ追加
溜めたバッファを NSString に変換する
Cの関数から呼ばれる不思議
*/
- (void)finishAppendCharacters:(NSString *)element {

NSString *currentString = [[[NSString alloc] initWithData:self.characterBuffer encoding:NSUTF8StringEncoding] autorelease];
NSLog(@"#%@ : %@", element, currentString);
[characterBuffer setLength:0];
}

/**
メモリ解放
*/
- (void)dealloc {
[characterBuffer release]; // ここ追加
[super dealloc];
}



で実行すると、こんな感じのログが出ると。

2010-10-05 14:54:57.747 Foo[1285:207] 受信中(データは分割されて受信される)
2010-10-05 14:54:57.749 Foo[1285:207] #pubDate : Thu, 30 Sep 2010 09:27:00 +0000
2010-10-05 14:54:57.750 Foo[1285:207] #title : Objective-C + libxml2 を使ってフィードを取り込みたいんだ (3)
2010-10-05 14:54:57.752 Foo[1285:207] #link : http://eyesrobe.blogspot.com/2010/09/objective-c-libxml2-3.html
2010-10-05 14:54:57.753 Foo[1285:207] #pubDate : Thu, 30 Sep 2010 07:27:00 +0000
2010-10-05 14:54:57.754 Foo[1285:207] #title : Objective-C + libxml2 を使ってフィードを取り込みたいんだ (2)
2010-10-05 14:54:57.781 Foo[1285:207] 受信中(データは分割されて受信される)
2010-10-05 14:54:57.782 Foo[1285:207] #link : http://eyesrobe.blogspot.com/2010/09/objective-c-libxml2-2.html
2010-10-05 14:54:57.783 Foo[1285:207] #pubDate : Thu, 30 Sep 2010 02:58:00 +0000
2010-10-05 14:54:57.784 Foo[1285:207] #title : Objective-C + libxml2 を使ってフィードを取り込みたいんだ (1)
2010-10-05 14:54:57.786 Foo[1285:207] 受信中(データは分割されて受信される)
2010-10-05 14:54:57.787 Foo[1285:207] #link : http://eyesrobe.blogspot.com/2010/09/objective-c-libxml2-1.html
2010-10-05 14:54:57.846 Foo[1285:207] 受信完了