2010年9月30日木曜日

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

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

libxml を使ってフィードを取り込む準備にかかろう。


パースするためのコンテクストとしてインスタンス変数を用意する。

@interface RSSImporter : NSObject {
// ここ追加
@private
xmlParserCtxtPtr context;
}

- (void)fromC; // ここ追加(でも、あとで消す)

@end


パーサ用のコンテクストを作る。←よくわかってない
サーバからデータを受信するごとにパーサに処理させたいのでxmlCreatePushParserCtxt()関数を使う。
xmlCreatePushParserCtxt()の第2引数で任意のデータとしてselfを指定しているのに注目。
通知されるC関数に対して、この任意のデータが引数で渡される。


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

//
// ここから普通に@implementationです
//
@implementation RSSImporter

/**
*/
- (id)init {

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

// ここを追加
// パーサ用のコンテクストを作る
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;
}


/**
ちょっとした確認に使う。いらなくなったら消す。
*/
- (void)fromC {
NSLog(@"C言語の関数からMethod呼び出せるって不思議");
}

- (void)dealloc {
[super dealloc];
}

#pragma mark NSURLConnection Delegate methods
//
// ここから下のメソッドが、NSURLConnectionのデリゲートメソッド
//

- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
// データ受信
NSLog(@"受信中(データは分割されて受信される)");

// これ追加
// 受信したデータをパーサに渡している
// NSDataはC言語では扱えないのでchar型にキャストして渡してる 
xmlParseChunk(context, (const char *)[data bytes], [data length], 0);
}

- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
// 受信完了
NSLog(@"受信完了");

// ここを追加
// パーサを解放
if (context) {
xmlFreeParserCtxt(context);
context = NULL;
}
}

- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
// 受信エラー
NSLog(@"エラー");

// ここを追加
// パーサを解放
if (context) {
xmlFreeParserCtxt(context);
context = NULL;
}
}

@end



xmlCreatePushParserCtxt()の第2引数で任意のデータとして指定しているselfは、
startElementSAX()やendElementSAX()やcharactersFoundSAX()などのC関数の第1引数で void *context として渡されてる。
イメージとしては、 context == self って感じ。
selfで渡されたデータを処理しやすいように、C関数の中でオブジェクトとして扱える形にすると、C関数の中でObjective-Cなメソッドを呼び出せるようになったり。
こんな感じで。RSSImporter *parserImporter = (RSSImporter *)context;


// 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;
[paserImporter fromC];
}



ビルドして実行。

続く。

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

前回書き忘れてた。Xcode 3.2.4 and iOS SDK 4.1 で試してる。
古いバージョンとか新しいバージョンのことは気にしない。

libxmlの前に NSURLConnection を使ってサーバにデータを取りにいく必要があるのでそっちから進める。
けど、説明は簡単に終わらせる。

NSURLConnection のメソッド initWithRequest:delegate: を呼び出すとサーバに接続して結果を返してくれる。
そんで、結果を返してくれる度にその結果に対応した NSURLConnection のデリゲートメソッドが実行されるようになってる。


@implementation RSSImporter

/**
とりあえず init に NSURLConnection の処理を書いてみる。
*/
- (id)init {

self = [super init];
if (self != nil) {
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;
}



- (void)dealloc {
[super dealloc];
}


#pragma mark NSURLConnection Delegate methods
//
// ここから下のメソッドが、NSURLConnectionのデリゲートメソッド
//

- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
// データ受信
NSLog(@"受信中(データは分割されて受信される)");
}

- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
// 受信完了
NSLog(@"受信完了");
}

- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
// 受信エラー
NSLog(@"エラー");
}

@end


NSURLConnection の説明終わり。
ここまでで取り敢えず実行させてみたい。
実行させるにはどこかでRSSImporterのインスタンスを作らないとなので、作る。
{YourProject}AppDelegate.m/.h でやることにする。


//
// {YourProject}AppDelegate.m
// コードの{YourProject}は自分のプロジェクト名に置き換えてね。
//
// ここでは、RSSImporterクラスの使い方の説明しようとしてるだけなの
// で、「// これ追加」ってところを自分の環境にあわせて追加すること。
//

#import <UIKit/UIKit.h>

@class {YourProject}ViewController;
@class RSSImporter; // これ追加

@interface {YourProject}AppDelegate : NSObject <UIApplicationDelegate> {
UIWindow *window;
{YourProject}ViewController *viewController;
RSSImporter *importer; // これ追加
}

@property (nonatomic, retain) IBOutlet UIWindow *window;
@property (nonatomic, retain) IBOutlet {YourProject}ViewController *viewController;
@property (nonatomic, retain) RSSImporter *importer; // これ追加

@end





//
// {YourProject}AppDelegate.h
// コードの{YourProject}は自分のプロジェクト名に置き換えてね
//
// ここでは、RSSImporterクラスの使い方の説明しようとしてるだけなの
// で、「// これ追加」ってところを自分の環境にあわせて追加すること。
//

#import "{YourProject}AppDelegate.h"
#import "{YourProject}ViewController.h"
#import "RSSImporter.h" // これ追加

@implementation {YourProject}AppDelegate

@synthesize window;
@synthesize viewController;
@synthesize importer; // これ追加

#pragma mark -
#pragma mark Application lifecycle

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

importer = [[RSSImporter alloc] init]; // これ追加

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

~(略)~

- (void)dealloc {
[importer release]; // これ追加
[viewController release];
[window release];
[super dealloc];
}

@end



編集終わったら、ビルドと実行を行う。
そうするとコンソールに、

[Session started at 2010-09-30 15:21:35 +0900.]
2010-09-30 15:21:39.928 Foo[1715:207] 受信中(データは分割されて受信される)
2010-09-30 15:21:39.931 Foo[1715:207] 受信中(データは分割されて受信される)
2010-09-30 15:21:39.962 Foo[1715:207] 受信中(データは分割されて受信される)
2010-09-30 15:21:39.964 Foo[1715:207] 受信中(データは分割されて受信される)
2010-09-30 15:21:39.966 Foo[1715:207] 受信中(データは分割されて受信される)
2010-09-30 15:21:39.967 Foo[1715:207] 受信完了


な感じで表示さるので、これで NSURLConnection については理解できたということにする。

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

理解するのにかなり苦しみました。
しかもその理解が正しいのかどうか今ひとつ確信がもてず。
でもまぁ気にせずに書いていこう。


前準備
  1. プロジェクト作成
  2. Frameworksにlibxml2.dylibを追加する。
  3. 「プロジェクト設定を編集」でビルドタブの設定の「ヘッダ検索パス」に/usr/include/libxml2を指定する。
  4. 「アクティブターゲット"hoge"を情報」でビルドタブの設定の「ヘッダ検索パス」に${SDKROOT},を指定(}の後ろのカンマも入れること)
  5. 新規ファイル作成でクラスファイル作成(NSObjectのサブクラス、ファイル名をRSSImporter)する。


では始める。
  • 4.で作ったRSSImporterの.h/.mファイルを使った説明がほとんどになる。
  • まずは、libxml2を使うときに定形文みたいに扱われるだろう関数とメソッドを書き上げてみる。
  • 見てみるとわかると思うけど、.mファイルがちょっと変わってる。
  • .mファイルには、C関数の定義と関数自体と構造体、それとクラスエクステンションが余分に書かれてる。
  • これから説明をしていこうと思うけど、あっち行ったりこっち行ったりしながらの話になるから、今どこのこと説明してるのか自分で分からなくなると思うけど、なるべく分かるようにやっていきたいな。



//
// RSSImporter.h
//

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

@interface RSSImporter : NSObject {
}

@end



//
// RSSImporter.m
//

#import "RSSImporter.h"
#import <libxml/tree.h>

//
// libxml2 (XMLのパーサライブラリ) を使うための準備
// libxml2もlibxml2のAPIもC言語で書かれているのでここはC言語で記述することになる
//

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);
static void endElementSAX(
void *context,
const xmlChar *localname,
const xmlChar *prefix,
const xmlChar *URI);
static void charactersFoundSAX(
void *context,
const xmlChar *characters,
int lenght);
static xmlSAXHandler simpleSAXHandlerStruct;

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) {
// 要素開始の通知を受けたときの処理
}

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

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

// SAXハンドラが定義されたxmlSAXHandler構造体に対して、使いたいハンドラを登録する
// (XMLパース中にXMLの要素や属性を発見したときに通知してほしいハンドラを登録する)
//
// 要素間のテキスト
// エラー
// 要素の開始
// 要素の終了
static xmlSAXHandler simpleSAXHandlerStruct = {
NULL, /* internalSubset */
NULL, /* isStandalone */
NULL, /* hasInternalSubset */
NULL, /* hasExternalSubset */
NULL, /* resolveEntity */
NULL, /* getEntity */
NULL, /* entityDecl */
NULL, /* notationDecl */
NULL, /* attributeDecl */
NULL, /* elementDecl */
NULL, /* unparsedEntityDecl */
NULL, /* setDocumentLocator */
NULL, /* startDocument */
NULL, /* endDocument */
NULL, /* startElement*/
NULL, /* endElement */
NULL, /* reference */
charactersFoundSAX, /* characters */
NULL, /* ignorableWhitespace */
NULL, /* processingInstruction */
NULL, /* comment */
NULL, /* warning */
NULL, /* error */
NULL, /* fatalError //: unused error() get all the errors */
NULL, /* getParameterEntity */
NULL, /* cdataBlock */
NULL, /* externalSubset */
XML_SAX2_MAGIC, //
NULL,
startElementSAX, /* startElementNs */
endElementSAX, /* endElementNs */
NULL, /* serror */
};

//
// クラスエクステンションを利用することで、
// プロパティとメソッドへのアクセスをプライベートからに制限できる
//

@interface RSSImporter ()
@end


//
// ここから普通に@implementationです
//

@implementation RSSImporter

- (void)dealloc {
[super dealloc];
}

#pragma mark NSURLConnection Delegate methods

- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
// データ受信
}

- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
// 受信完了
}

- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
// 受信エラー
}

@end


続く。

2010年9月22日水曜日

アプリ内で登録してもらうパスワードの保存にKeychain使ってみた

「iPhone SDK 開発のレシピ」で紹介されてたのを使ったら驚くほど簡単にキーチェーンに保存できた。

紹介されてたの、これ。
http://github.com/ldandersen/scifihifi-iphone/tree/master/security/

2010年9月16日木曜日

実機ビルドでプロビジョニングのエラーっぽいのでビルドに失敗するとき

the executable was signed with invalid entitlements

The entitlements specified in your application’s Code Signing Entitlements file do not match those specified in your provisioning profile. (0xE8008016)

なエラーが出る。実機へのビルドと実行のとき。

以前は問題なかったので、原因がわからない。

たいていはリリース用にビルドするのと同じように、

1.buildフォルダを削除する。。
2.ビルド>すべてのターゲットをクリーニング

を行った上でビルドと実行を行えばエラー出なくなるんじゃないかと。
少なくとも今回はこれでエラー出なくなった。

2010年9月14日火曜日

static と const

static変数について
Objective-Cにはクラス変数ないから、クラスの実装ファイルにstatic変数を宣言する方法でstatic変数をクラス変数っぽく使えるんじゃないかってことか。


constについて
変数の頭にconstつけて値も代入した状態で宣言すれば、値が書き換えられない変数になるようです。

@property

プロパティとインスタンス変数って別物っぽい。プロパティって何?よくわからないので宣言プロパティ(@property)の特徴だけ調べた。

宣言プロパティ(@property)でインスタンス変数を指定すると。@synthesizeで定型的なアクセサメソッドを生成できる。それと、アクセサメソッドをドット演算子を使って呼び出せる。


インスタンス変数を直接操作していい場合って?よくわからん。

メソッド内でのselfの使用は?
メソッド内で、そのインスタンス自身のアクセサを利用するためにselfに対してドット演算子を適用できる。でもアクセサメソッド内では使わないこと、使うと再帰呼び出しとなり停止しなくなる。


例えば、@propertyでretainを指定の場合


// hoge.h

NSString name_;
@property (nonatomic, retain) NSString name;



// hoge.m

@implementation Item
@synthesize name = name_;
@end;



って感じで、
@synthesizeを指定することにより以下のアクセサメソッド(セッタとゲッタ)は自分で書かなくてもよいこととなる。



- (NSString *)name {
return name_;
}

- (void)setName:(NSString *)name
{
if (name_ != name) {
[name_ release];
name_ = [name retain];
}
}



超参考になる
http://iphone.longearth.net/2009/01/22/【objective-c】nsdate-dateでセットした変数が参照できずエラー/
http://iphone-dev.g.hatena.ne.jp/tokorom/20090706/1246890179


// おまけ:retainカウントの取り方
[obj retainCount]

2010年9月13日月曜日

MacBook ProのHDDをSSDに換えてよかった

MacBook ProのHDDをSSDに換えてよかったのは、HDDよりも物理的な破損が起こりにくいだろうってこと。

他には、
ファイル読み込みが早いこと。
アプリの起動が早いこと。
スリープからの復帰が早いこと。

2010年9月7日火曜日

NSManagedObject について知りたい

ここの説明がわからせてくれた。

CoreData3分クッキング
http://www.spice-of-life.net/wiki/index.cgi?CoreDataCooking


NSManagedObjectが引っ張ってきたデータ、NSFetchRequestが取得したいデータの条件を指定するクラスです。もう一つ名前からして何をするのかわからないNSManagedObjectContextというクラスがありますが、実はCore Dataのうち最も重要なクラスで、これがメモリ上のデータべースです。

2010年9月6日月曜日

Objective-C プロトコルを分かってないよ

プロトコルを分かってない。プロトコルはこんな感じらしい。


オブジェクトの振る舞いを表すメソッドの集合をプロトコルと呼ぶ。
うまく利用すると、柔軟性、独立性の高いクラス定義が可能となる。

うーむ、、、で、
なんとなく理解したこと。間違ってるかもだけど。

プロトコルは、いろんなとこで使われる。
いろんなとこで共通して使われるメソッドがプロトコルに収められている。
例えば、何かについてのプロトコルを作るとすると、「何かをセーブする」とか「何かロードする」とか「何かをあーしてこーする」とか何かについて共通して利用されるメソッドたちをプロトコルの中に決めておく。
プロトコルでのメソッドは宣言だけ。実装は各クラス。
@optional なメソッドであれば実装してもしなくてもよい。
@required または指定ないメソッドであれば実装が必須。

こんな感じ。

2010年9月1日水曜日

2010年8月4日 Magic Tracpad 買ったった

発売されて10日くらい後に購入。
新製品はたいていの場合は、様子見してるんだけど買ったった。
yodobashi.comからヨドバシAkibaの店頭受け取りで予約して。今のところまだApple製品はyodobashi.comから直接は買えない様子。

でだ、あまり使ってない。
でも後悔してない。
MacBook Proは基本、膝の上に置かれてるけど、たまに机の上でディスプレイ繋いでつかったりするの。
そんとき用。
ワイアレスキーボードの横に並べたらピッタリだったよ。
ほんとはキーボードの下に縦に並べたいんだけど、横でもまぁいいかって感触だった。

そうそう、Windows7のネットブックにも繋げてみた。
ドライバはBootCamp用のドライバを引っこ抜いて入れる感じで、裏技的。
まだWindows用のドライバは熟成されてないね。
タップを無効にできるだけでもいいんだけどなぁ。

そんな感じ。