2010年12月14日火曜日

RSS2.0 の pubDate に入ってる日付を NSDate に変換したい。

Objective-C での変換のはなし。 Xcode 3.2.5 and iOS SDK 4.2.1 で試してる。
文字列の日付を NSDate に変換します。


こんな感じで。

NSString *stringPubDate = @"Tue, 30 Nov 2010 06:54:00 +0000";

NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setDateFormat:@"EEE, dd MMM yyyy HH:mm:ss ZZZZ"];
NSDate *pubDate = [formatter dateFromString:stringPubDate];
[formatter release];

NSLog(@"%@", pubDate);



EEEを探すのに苦労した。
記号の一覧がまとまってるサイトを探してたけど、見つけられなかった。

EEEが見つかる前は仕方ない、こんな感じにしようと思った。

NSString *stringPubDate = @"Tue, 30 Nov 2010 06:54:00 +0000";

NSArray *splitDate = [stringPubDate componentsSeparatedByString:@", "];

NSDictionary *weekDict = [NSDictionary dictionaryWithObjectsAndKeys:
@"月", @"Mon",
@"火", @"Tue",
@"水", @"Wed",
@"木", @"Thu",
@"金", @"Fri",
@"土", @"Sat",
@"日", @"Sun",nil];

NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setDateFormat:@"dd MMM yyyy HH:mm:ss ZZZZ"];
NSDate *pubDate = [formatter dateFromString:[splitDate objectAtIndex:1]];
[formatter release];

NSLog(@"%@ %@曜日", pubDate, [weekDict objectForKey:[splitDate objectAtIndex:0]]);


ついでなので、NSDate から文字列に変換してみる。

NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setTimeZone:[NSTimeZone timeZoneWithName:@"Asia/Tokyo"]];
[dateFormatter setDateFormat:@"yyyy年MM月dd日HH時mm分ss秒"];
NSString *stringPubDate = [dateFormatter stringFromDate:pubDate];
[dateFormatter release];

NSLog(@"pubDate [ %@ ] [ %@ ]", pubDate, stringPubDate);

2010年11月30日火曜日

Moneyha 1.0.2 を申請した

昨日の夕方。
iOS SDK 4.2.1 でリビルドしただけのバージョン。

次期バージョンとしていくつか新機能を試していたけど、ちょっと考え直す。
締め日を変更できるようにして欲しいっていうレビューあり。
簡単に対応できるか調べてみる。

設定ファイルをplistで用意しておいて読み書きさせたい

Xcodeに追加したplistファイル。
これをアプリ側で読み書きさせたい。

こうやるっぽい。
・Document ディレクトリーにplistファイルをコピーして使う。

合ってるかわからないが、こうやってみた。
・plistファイルをコピーするDocument ディレクトリへのパスを取得する。
・そのパスにファイルがあるか確認する。
・ない。アプリを初めて起動するときは、ない。
・そのパスのファイルを作成する。(plistファイルをコピーして作成する。)
・以降は作成した(Document ディレクトリにある)ファイルを読み書きする。



//
// かなーりコードを省略してます。当然ですがコピペしても動きませんよ。
//
@interface HogeAppDelegate : NSObject
{
@private
NSString *filepathToHogePlist_;
}

@property (nonatomic, retain, readonly) NSString *filepathToHogePlist;

- (NSString *)applicationDocumentsDirectory;

@end


//
// かなーりコードを省略してます。当然ですがコピペしても動きませんよ。
//
@implementation HogeAppDelegate

@synthesize filepathToHogePlist = filepathToHogePlist_;

//
// [self applicationDocumentsDirectory] でDocument ディレクトリへのパスが取得できる
//
- (NSString *)applicationDocumentsDirectory
{
return [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
}

//
// アプリを初めて使うときに、plistをコピーしている。
//
// NSArray *hoges = [NSArray arrayWithContentsOfFile:self.filepathToHogePlist];
// とか
// [hogehogehoge writeToFile:self.filepathToHogePlist atomically:YES];
// みたいな感じで処理できるようになる。
//
- (NSString *)filepathToHogePlist
{
if (filepathToHogePlist_!=nil) {
return filepathToHogePlist_;
}

BOOL success;
NSFileManager* fileManager = [NSFileManager defaultManager];
filepathToHogePlist_ = [[self applicationDocumentsDirectory] stringByAppendingPathComponent:@"Hoge.plist"];

// アプリで用意したファイル(これをDocumentDirectoryにコピーして、以降はコピーしたファイルを読み書きさせる)
success = [fileManager fileExistsAtPath:filepathToHogePlist_];
if (!success)
{
NSBundle* bundle = [NSBundle mainBundle];
NSString* bundlepath = [bundle pathForResource:@"Hoge" ofType:@"plist"];
NSMutableArray *hoges = [NSMutableArray arrayWithContentsOfFile:bundlepath];
[hoges writeToFile:filepathToHogePlist_ atomically:YES];
}

return [filepathToHogePlist_ retain];
}

@end



こんな感じで。なんとなく。

で、iOSシミュレータの場合のDocumentDirectoryの場所
~/Library/Application Support/iPhone Simulator/バージョン/Applications/ランダムな文字列/Documents/Hoge.plist

2010年11月8日月曜日

View-based Application で CoreData を使えるようしたい

新規プロジェクト作成のテンプレートでView-based Applicationを選択してプロジェクトを作成する。Core Dataを使いたいがどうすればいいのか。
Navigation-based Applicationテンプレートを選ぶ場合はCore Dataを使うかどうか選択できるようになってるけど、View-basedではチェックボックスが出てこない。
それならば自分で実装しようということになるのだけれど、うーんうーんって感じになる場合が多いと思うの。

なので手順を書いておく。

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

新規プロジェクトをView-based Applicationを選択して名前をMyCDataとして作成してます。


僕がやったポイント。

  1. CoreData.frameworkを追加
  2. DataModel(.xcdatamodel)ファイルを新規作成
  3. DataModelにエンティティ作ってプロパティを追加
  4. .xcdatamodelを.xcdatamodeldにバンドル
  5. Managed Object Class(NSManagedObjectサブクラス)ファイルを新規作成
  6. おまじないのコード(NSManagedObjectContextなどを用意)
  7. 後は普通にコード書く
  8. データの読み込みや追加などは、用意したコンテクスト(NSManagedObjectContext)を通じて実行



1.Frameworksグループを右クリック>追加>既存のフレームワーク>CoreData.frameworkを追加

2.Resourcesグループを右クリック>追加>新規ファイル>DataModelを選択>MyCData.xcdatamodelと名前付けて次へでそのまま完了。

3.MyCData.xcdatamodelにエンティティPersonを追加して、そのプロパティにageとnameを追加。

データ型を
age は Int16
name は String
とした。

4.MyCData.xcdatamodelを選択した状態で、設計>データモデル>モデルバージョンを追加。

MyCData.xcdatamodeldにMyCData.xcdatamodelがバンドルされた形になる。
MyCData.xcdatamodeldの中にxcdatamodelファイルが1つ追加されるけどいらないので削除する。

※この4.をやらないで対応する方法もあるけれどそれは書かない。将来的にモデルの変更(プロパティの追加など)がされる可能性を考えれならこれやっとけばいいじゃん、必要になるでしょってことで。


5.エンティティPersonを選択した状態でファイル追加をするとManaged Object Classが選べるので、そのまま完了するとPerson.h/.mが追加される。

//
// Person.h
//
#import <CoreData/CoreData.h>


@interface Person : NSManagedObject
{
}

@property (nonatomic, retain) NSNumber * age;
@property (nonatomic, retain) NSString * name;

@end


//
// Person.m
//

#import "Person.h"


@implementation Person

@dynamic age;
@dynamic name;

@end



6.おまじないコード


//
// MyCDataAppDelegate.h
//
#import <UIKit/UIKit.h>

// ここ追加
#import <CoreData/CoreData.h>

@class MyCDataViewController;

@interface MyCDataAppDelegate : NSObject <UIApplicationDelegate> {
UIWindow *window;
MyCDataViewController *viewController;
// ここ追加
@private
NSManagedObjectContext *managedObjectContext_;
NSManagedObjectModel *managedObjectModel_;
NSPersistentStoreCoordinator *persistentStoreCoordinator_;
}

@property (nonatomic, retain) IBOutlet UIWindow *window;
@property (nonatomic, retain) IBOutlet MyCDataViewController *viewController;

// ここ追加
@property (nonatomic, retain, readonly) NSManagedObjectContext *managedObjectContext;
@property (nonatomic, retain, readonly) NSManagedObjectModel *managedObjectModel;
@property (nonatomic, retain, readonly) NSPersistentStoreCoordinator *persistentStoreCoordinator;

// ここ追加
- (NSString *)applicationDocumentsDirectory;

@end


//
// MyCDataAppDelegate.m
//
#import "MyCDataAppDelegate.h"
#import "MyCDataViewController.h"

@implementation MyCDataAppDelegate

@synthesize window;
@synthesize viewController;


#pragma mark -
#pragma mark Application lifecycle

/**
これ追加
*/
- (void)awakeFromNib {
viewController.managedObjectContext = self.managedObjectContext;
}

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[window addSubview:viewController.view];
[window makeKeyAndVisible];
return YES;
}

- (void)applicationWillResignActive:(UIApplication *)application {
}

- (void)applicationDidEnterBackground:(UIApplication *)application {
}

- (void)applicationWillEnterForeground:(UIApplication *)application {
}

- (void)applicationDidBecomeActive:(UIApplication *)application {
}

- (void)applicationWillTerminate:(UIApplication *)application {
}


#pragma mark -
#pragma mark Memory management

- (void)applicationDidReceiveMemoryWarning:(UIApplication *)application {
}

- (void)dealloc {
[viewController release];
[window release];
[super dealloc];
}


#pragma mark -
#pragma mark Core Data stack

/**
これ追加(ゲッター)
CoreData用に共通のContextを1つここで用意する。
SQLiteファイルへの永続ストアコーディネータ(NSPersistentStoreCoordinator)のインスタンスを、用意したContextにセットする。
*/
- (NSManagedObjectContext *)managedObjectContext {

if (managedObjectContext_ != nil) {
return managedObjectContext_;
}

NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
if (coordinator != nil) {
managedObjectContext_ = [[NSManagedObjectContext alloc] init];
[managedObjectContext_ setPersistentStoreCoordinator:coordinator];
}
return managedObjectContext_;
}


/**
これ追加(ゲッター)
.xcdatamodeld (.xcdatamodelを内包した)バンドルを指定する。
管理オブジェクトモデル(NSManagedObjectModel)の準備をする。
この管理オブジェクトモデルを使うことで、管理オブジェクト(アプリ側)とレコード(データベース側)とのマッピングさせる。
*/
- (NSManagedObjectModel *)managedObjectModel {

if (managedObjectModel_ != nil) {
return managedObjectModel_;
}

NSString *modelPath = [[NSBundle mainBundle] pathForResource:@"MyCData" ofType:@"momd"];
NSLog(@"データモデルのバンドルディレクトリ(?)のパス %@", modelPath);
NSURL *modelURL = [NSURL fileURLWithPath:modelPath];
managedObjectModel_ = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];

return managedObjectModel_;
}

/**
これ追加(ゲッター)
永続ストアとしてSQLite(ファイル)を指定する。
この永続ストアコーディネータを使うことで、管理オブジェクトモデル(.xcdatamodel)と永続ストアを関連付けて管理できるようになる。
*/
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {

if (persistentStoreCoordinator_ != nil) {
return persistentStoreCoordinator_;
}

NSURL *storeURL = [NSURL fileURLWithPath: [[self applicationDocumentsDirectory] stringByAppendingPathComponent: @"MyCData.sqlite"]];


NSError *error = nil;
persistentStoreCoordinator_ = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];

if (![persistentStoreCoordinator_ addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error]) {
NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
abort();
}

return persistentStoreCoordinator_;
}


#pragma mark -
#pragma mark Application's Documents directory

/**
これ追加
Returns the path to the application's Documents directory.
*/
- (NSString *)applicationDocumentsDirectory {
return [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
}


@end

7.後は普通にコード書く
8.データの読み込みや追加などは、用意したコンテクスト(NSManagedObjectContext)を通じて実行

//
// MyCDataViewController.h
//
#import <UIKit/UIKit.h>

// ここ追加
#import <CoreData/CoreData.h>

@interface MyCDataViewController : UIViewController {
// ここ追加
NSFetchedResultsController *fetchedResultsController_;
NSManagedObjectContext *managedObjectContext_;

// ここ追加
IBOutlet UIButton *addButton;
IBOutlet UIButton *requestButton;
NSMutableArray *persons;
}


// ここ追加
@property (nonatomic, retain) NSManagedObjectContext *managedObjectContext;
@property (nonatomic, retain) NSFetchedResultsController *fetchedResultsController;

// ここ追加
@property (nonatomic, retain) IBOutlet UIButton *addButton;
@property (nonatomic, retain) IBOutlet UIButton *requestButton;
@property (nonatomic, retain) NSMutableArray *persons;

- (IBAction)addPerson:(id)sender;
- (IBAction)requestPersons:(id)sender;

@end


//
// MyCDataViewController.m
//
#import "MyCDataViewController.h"

// ここ追加
#import "Person.h"

@implementation MyCDataViewController

// ここ追加
@synthesize fetchedResultsController = fetchedResultsController_;
@synthesize managedObjectContext = managedObjectContext_;
@synthesize addButton;
@synthesize requestButton;
@synthesize persons;

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

- (void)viewDidUnload {
// ここ追加
self.addButton = nil;
self.requestButton = nil;
}

- (void)dealloc {
// ここ追加
[addButton release];
[requestButton release];
[persons release];

// ここ追加
[fetchedResultsController_ release];
[managedObjectContext_ release];

[super dealloc];
}


/**
これ追加
*/
- (IBAction)addPerson:(id)sender
{
NSLog(@"- (IBAction)addPerson:(id)sender");
NSManagedObjectContext *context = self.managedObjectContext;

Person *person = (Person *)[NSEntityDescription insertNewObjectForEntityForName:@"Person" inManagedObjectContext:context];

[person setAge:[NSNumber numberWithInt:37]];
[person setName:@"Jack"];

NSError *error;
if (![context save:&error]) {
NSLog(@"Error");
}
}

/**
これ追加
*/
- (IBAction)requestPersons:(id)sender
{
NSLog(@"- (IBAction)requestPersons:(id)sender");
NSManagedObjectContext *context = self.managedObjectContext;

NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:@"Person" inManagedObjectContext:context];
[request setEntity:entity];

// ソート
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"name" ascending:YES];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
[request setSortDescriptors:sortDescriptors];
[sortDescriptor release];
[sortDescriptors release];


NSError *error;
NSMutableArray *mutableFetchResults = [[context executeFetchRequest:request error:&error] mutableCopy];
if (mutableFetchResults == nil) {
NSLog(@"no results");
}

[self setPersons:mutableFetchResults];
[mutableFetchResults release];
[request release];


for (Person *person in persons) {
NSLog(@"%@", [person name]);
}
}

@end

後このサンプルでは、InterfaceBuilderでMyCDataViewController.xibにボタン2つ追加してそれぞれIBOutletとIBActionを繋げれば動くはず。

2010年11月2日火曜日

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

なんかもうlibxml2のことは関係なく普通にObjective-Cの勉強になってる。

今回は、
  • NSOperationQueue
  • NSOperation
  • NSNotificationCenter
  • NSNotification

に焦点をあわせる。

でもNSNotificationの辺りは
「Cocoa Notification (NSNotification,NSNotificationCenter)」と「キー値監視」(1~4)
に書いてるので省略。
1つだけ注意するのは、通知の受け取りをメインスレッドで行ないたい場合は、通知する側でperformSelectorOnMainThread:withObject:を使って通知を投げる必要あり。


NSOperationQueueとNSOperationについてもあまり書く事はないんだけど続ける。
覚えたこと。
  • 並列と非並列とがある。
  • キューにNSOperationのインスタンスを貯めて置いて、順番に処理を起動していく。
  • 並列は、1つの処理の完了を待たずに次の処理を起動する。(複数の別スレッドで処理される)
  • 非並列は、1つの処理が完全に終了するまで次の処理を起動しない。(全てメインスレッドで処理される)
  • iOS4からは[[NSOperationQueue alloc] init]で初期化した場合は、並列になる。NSOperationなインスタンスのisConcurrentの値は関係なく。
  • iOS4で非並列に処理させたい場合は、[NSOperation mainQueue] ってやる。


注意すること。
並列で処理実行するときは、NSRunLoopの実行ループをループさせておいてのイベント処理が必要な場合がある(間違ってるかも)。
今回は必要なのでRunLoop入れてあります。


あーもう、ぐちゃぐちゃだーーー!!!
あとはソース見てください。


次回の内容は未定。



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

新規プロジェクトをView-based Applicationを選択して名前をMyOperationとして作成してます。
新規ファイル追加で、NSOperationを継承したMyClassを作成しています。


//
// MyOperationViewController.h
//
#import

@interface MyOperationViewController : UIViewController {
}
@end


//
// MyOperationViewController.m
//
#import "MyOperationViewController.h"
#import "MyClass.h"

@implementation MyOperationViewController

- (void)owatayo:(MyClass *)obj
{
//NSLog(@"%@", obj);
//NSLog(@"no.%d", [[(MyClass *)[obj object] number] intValue]);
NSNumber *num = [[obj userInfo] objectForKey:@"number"];
NSLog(@"[no.%d] メインスレッド:通知owataを受け取ってowatayoメソッドを実行したよ。", [num intValue]);
}

- (void)viewDidLoad
{
[super viewDidLoad];

// 読み込むフィード
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];

// 通知
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];

// キューの初期化
NSOperationQueue *queue = [[NSOperationQueue alloc] init];

// NSOperationQueue に許す同時接続スレッドの数
// スレッド数を制限した場合としない場合の実行内容を比較してみるといいかも。
[queue setMaxConcurrentOperationCount:3];

// キューにNSOperationなオブジェクトを追加する。
// 追加されたオブジェクは別スレッドで順に処理されていく。
int i=0;
for (NSString *feedURL in feeds) {

NSURLRequest *requestURL = [NSURLRequest requestWithURL:[NSURL URLWithString:feedURL]];
MyClass *obj = [[MyClass alloc] initWithRequest:requestURL];
obj.number = [NSNumber numberWithInt:(++i)];
[center addObserver:self selector:@selector(owatayo:) name:@"owata" object:obj];
[queue addOperation:obj];
[obj release];
}

// キューが全て処理されるまで待つメソッド
// [queue waitUntilAllOperationsAreFinished];
//
// 待たないで処理をバックグラウンドで処理するのであれば、NSNotificationCenterなど
// を使ってバックグラウンド処理完了の通知を受け取ることが多いみたい。

[queue release];
}

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

- (void)viewDidUnload {
}

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

@end


//
// MyClass.h
//
#import

@interface MyClass : NSOperation {
NSNumber *number;
NSURLRequest *requestURL;

BOOL done;
}

@property (nonatomic, retain) NSNumber *number;
@property (nonatomic, retain) NSURLRequest *requestURL;

- (id)initWithRequest:(NSURLRequest *)requestURL_;

@end


//
// MyClass.m
//
#import "MyClass.h"

@implementation MyClass

@synthesize number;
@synthesize requestURL;

/**
* 初期化
*/
- (id)initWithRequest:(NSURLRequest *)requestURL_
{
self = [super init];
if (self != nil) {
self.requestURL = requestURL_;
}

return self;
}

/**
* キューに追加されることで実行されるメソッド
*/
- (void)main
{
NSLog(@"[No.%d] MyClass::main", [self.number intValue]);

// NSURLConnection
NSURLConnection *urlConnection = [[[NSURLConnection alloc] initWithRequest:self.requestURL delegate:self] autorelease];

if (urlConnection == nil) {
NSLog(@"error");
}
else {
// データ受信完了フラグ
done = NO;

// NSOperationQueueで別のスレッド動かす場合、NSAutoreleasePoolは要らないみたいだけど、NSRunLoop(実行ループ?)は必要っぽい。
// 処理実行中(done==NO)の間は、実行ループをループさせ続けることが必要。
// ここでは、NSURLConnectionによるデータ受信完了時点で処理完了(done=YES)としている。
do {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
} while (!done);
}

// 通知するときに一緒に送りたいメッセージ
NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:number, @"number", @"value", @"key", nil];

// 処理が終わったら通知メッセージを送る。
// メインスレッドで実行させたいので performSelectorOnMainThread:withObject:waitUntilDone: を使った。
NSNotification *notification = [NSNotification notificationWithName:@"owata" object:self userInfo:dict];
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
[center performSelectorOnMainThread:@selector(postNotification:) withObject:notification waitUntilDone:NO];
}

/**
* データ受信
*/
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
NSLog(@"[No.%d] 受信中(データは分割されて受信される)", [self.number intValue]);
}

/**
* 受信完了
*/
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
NSLog(@"[No.%d] 受信完了", [self.number intValue]);
done = YES;
}

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

/**
* dealloc
*/
- (void)dealloc {
[number release];
[requestURL release];
[super dealloc];
}

@end


たぶん続く。

2010年10月29日金曜日

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

続きに何を書けばいいのかわからなくなった。

NSOperationQueue + NSOperation を使って並列に複数フィードの読み込みをやってみればいいのか。
通知のこと調べたりスレッドセーフのこと調べてたりしてるうちに、お腹いっぱいになっちゃった。

次に書く内容のポイント
・NSOperationQueueとNSOperation
・NSNotificationとNSNotificationCenter

あとは何だろう。今日はここまで。

続く。

2010年10月27日水曜日

「Cocoa Notification (NSNotification,NSNotificationCenter)」と「キー値監視」(5)

少し時間があいたけど放置せずに続き書く。
前回まではNSNotificationCenterを使った通知の説明だったと思うので、次にキー値監視(Key-Value Observing)(略はKVO)に行く。

調べてて物凄く混乱したりして、今も混乱中ではあるけど気にしない。
混乱していた最大の原因は、キー値コーディング(Key-Value Coding)(略はKVC)の知識なしにKVOを調べようとしていたこと。
KVCありきのKVOです。

で、キー値コーディングってどんなのってことになるけど、自分が現状理解している範囲で説明するとする。
プロパティとかインスタンス変数の扱い方で、それらの名前(文字列)をキーにして値の出し入れをできるようする手段。って感じのよう。
注意点は、アクセサがあればアクセサを経由で値を扱ってくれて、アクセサがなければないで勝手に値の出し入れをやってくれる。
キー値監視のサンプルコードなどを見てると、キー値監視用のプロパティに対するアクセサがあったりなかったりなので、自分混乱しまくりでした。

キー値監視については、指定したプロパティなどの値が変化したら通知してくれる。
キー値監視のサンプルコード見ててもう一つ混乱したのが、プロパティの値の変更はキー値コーディング的に行われてる場合と普通のセッター使って行われてる場合があるっぽいこと。


それと、一つ前の記事『非同期処理と通知とスレッドセーフ』でも書いたけど、バックグラウンド処理とキー値監視を組み合わせてる場合にはスレッドセーフかどうかを気にしましょう。
プロパティの値の変更(オブザーバへ通知)からオブザーバでの通知受取後の処理までの一連の処理は、通知元のスレッドIDで実行されてたりします。

疲れたのでサンプル載せて「キー値監視」のことはもう終わりにしちゃう。
ハッキリ言って、「キー値コーディング」「キー値監視」は奥が深そうです。
こんなわたしのブログ見るよりまともな参考書で勉強すべきだ、とういうのが今回の結論です。


ここで『「Cocoa Notification (NSNotification,NSNotificationCenter)」と「キー値監視」』について終わり。
混乱は収束しないです。


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

新規プロジェクトをView-based Applicationを選択して名前をMyKvoとして作成してます。
新規ファイル追加で、NSObjectを継承したcreatureを作成しています。

// MyKvoViewController.h

#import <UIKit/UIKit.h>

@class creature;

@interface MyKvoViewController : UIViewController {
creature *human;
}

@property (nonatomic, retain) creature *human;

@end


// MyKvoViewController.m

#import "MyKvoViewController.h"
#import "creature.h"

@implementation MyKvoViewController

@synthesize human;

- (void)viewDidLoad {
[super viewDidLoad];

human = [[creature alloc] init];
human.sex = @"male";

// humanオブジェクトのcommentプロパティの値に変化があれば selfに通知されるようにする。
// ここではselfがオブザーバとなる。
[human addObserver:self forKeyPath:@"comment" options:(NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld) context:NULL];

// いろいろな方法でhumanのcommentプロパティの値を変化させてみよう。
human.comment = @"I am a man.";
[human setComment:@"I'm a male."];
[human setValue:@"I'm not sure." forKey:@"comment"];
[human setIsHavingSausage:YES];
[human setIsHavingSausage:NO];

// commentのキー値変更のselfに対する通知を停止する。
// addObserver に対して removeObserver。使わなくなったキー値監視は消すように。
[human removeObserver:self forKeyPath:@"comment"];
human.comment = @"I am a man.";
}

/**
* ここで通知を受けとる
*/
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if ([keyPath isEqual:@"comment"]) {
NSLog(@"通知受信 comment:%@", [(creature *)object comment]);
}
}

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

- (void)viewDidUnload {
}

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

@end


// creature.h

#import <Foundation/Foundation.h>

@interface creature : NSObject {
NSString *comment;
NSString *sex;
BOOL isHavingSausage;
}

@property (nonatomic, retain) NSString *comment;
@property (nonatomic, retain) NSString *sex;

- (void)setIsHavingSausage:(BOOL)isHavingSausage_;

@end


// creature.m

#import "creature.h"

@implementation creature

@synthesize comment;
@synthesize sex;

- (void)setIsHavingSausage:(BOOL)isHavingSausage_
{
if (isHavingSausage_ && [self.sex isEqualToString:@"male"]) {
self.comment = @"I am a real man.";
} else {
self.comment= @"I'm a woman!";
}

isHavingSausage = isHavingSausage_;
}

- (void)dealloc {
[comment release];
[sex release];
[super dealloc];
}

@end



続かない。

2010年10月20日水曜日

非同期処理と通知とスレッドセーフ

NSOperationQueue と KVO ( Key-Value Observing )を組み合わせればスレッドセーフかどうかを気にしなくていで実装できるのかも、と期待してみたが違うようだ。

監視と通知は、それぞれのオブジェクトのスレッドで行われるらしい。

NSOperationQueue と NSNotificationCenter と NSObjectの performSelectorOnMainThread:withObject:waitUntilDone: の組み合わせが良さそう。


KVO しているプロパティがバックグラウンドスレッドで変更されたら、オブザーバのメソッドも同じバックグラウンドスレッドで呼び出されます。
UIKit とスレッドのお約束 - Elegant Apps Developers




2010-10-26 追記:それと、KVO じゃなくて NSOperation の話になるけれども以下のことを付け足しておく。

メインスレッドから [queue cancelAllOperations] などとして、別スレッドでバックグラウンド実行中処理の [myOperation cancel] が発動されると、[myOperation cancel] はメインスレッドで実行される。そんでそのバックグラウンド実行中のものは別スレッドでそのまま動いているから、どっちがどっちって感じになってしまうと。

キャンセル>即処理終了、ってことではないから、isCancelled とか使ってキャンセルされるときのことも考えて書いていこうね。ってことで。

2010年10月19日火曜日

「Cocoa Notification (NSNotification,NSNotificationCenter)」と「キー値監視」(4)

前回の記事も何となく説明の仕方が間違ってるような気がする。
サンプルのコードが悪いのか、それともそもそも根本的に間違っているのか。

とりあえず、サンプルのコードを書き直してみた。

新規プロジェクトをView-based Applicationを選択して名前をMyNotificationとして作成してます。
新規ファイル追加で、NSObjectを継承したMyNotificationを作成しています。

「Cocoa Notification (NSNotification,NSNotificationCenter)」を試しなおす。
説明できることはほとんど前回までと変わらないのでサンプルコードだけ載せる。


// MyNotificationViewController.h

#import <UIKit/UIKit.h>

@interface MyNotificationViewController : UIViewController {
NSInteger count;
NSTimer *timer;
}

@end


// MyNotificationViewController.m

#import "MyNotificationViewController.h"
#import "MyNotification.h"

@implementation MyNotificationViewController

/**
* 新しくスレッドをたてて、myNotificationのmyWorkメソッドを実行する。
*/
- (void)newThread
{
count++;

MyNotification *myNotification = [[[MyNotification alloc] init] autorelease];
[NSThread detachNewThreadSelector:@selector(myWork:) toTarget:myNotification withObject:[NSString stringWithFormat:@"%d",count]];

if(count > 2) {
[timer invalidate];
timer = nil;
}
}

/**
* 通知受け取ったときに呼ばれる処理
*/
- (void)owatayo:(id)info
{
NSDictionary *userInfo = (NSDictionary *)[info userInfo];
NSLog(@"メインスレッド:(no.%@スレッドから)通知owataを受け取りowatayoメソッドを実行。", [userInfo objectForKey:@"threadCount"]);
}

- (void)viewDidLoad
{
[super viewDidLoad];

NSLog(@"メインスレッド:処理開始");

// NSNotificationCenterのインスタンスに、
// addObserver : 通知を受け取るオブジェクト(ここでは自分自身)
// selector : 通知を受けたときに実行するメソッド
// name : 通知される通知名
// object : どのオブジェクトからの通知を受け取るのか指定できる。nilであれば限定しない。
NSNotificationCenter *center;
center = [NSNotificationCenter defaultCenter];
[center addObserver:self selector:@selector(owatayo:) name:@"owata" object:nil];


NSLog(@"新スレッドを0.5秒間隔で作成します。");
count = 0;
timer = [NSTimer scheduledTimerWithTimeInterval:1.0f target:self selector:@selector(newThread) userInfo:nil repeats:YES];

NSLog(@"メインスレッド:処理終了");
}

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

- (void)viewDidUnload {
}

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

@end


// MyNotification.h

#import <Foundation/Foundation.h>

@interface MyNotification : NSObject {
}

- (void)myWork:(NSString *)num;
- (void)didMyWork:(NSString *)threadCount;

@end


// MyNotification.m

#import "MyNotification.h"

@implementation MyNotification

/*
* 新スレッドでの処理
*/
- (void)myWork:(NSString *)threadCount
{
NSLog(@"(no.%@)新スレッドの処理開始", threadCount);

NSAutoreleasePool *pool;
pool = [[NSAutoreleasePool alloc] init];
[NSThread sleepForTimeInterval:3.0];
[self didMyWork:threadCount];
[NSThread sleepForTimeInterval:3.0];
[pool release];

NSLog(@"(no.%@)スレッド破棄", threadCount);
[NSThread exit];
}

/*
* 新スレッド終了するときの通知させる処理
*/
- (void)didMyWork:(NSString *)threadCount
{
NSDictionary *myInfo = [NSDictionary dictionaryWithObjectsAndKeys:threadCount, @"threadCount", nil];

// 通知する内容を指定
NSNotification *notification;
notification = [NSNotification notificationWithName:@"owata" object:self userInfo:myInfo];

// 通知する(メインスレッドから通知する)
NSNotificationCenter *center= [NSNotificationCenter defaultCenter];
[center performSelectorOnMainThread:@selector(postNotification:) withObject:notification waitUntilDone:NO];
}

@end

2010年10月18日月曜日

「Cocoa Notification (NSNotification,NSNotificationCenter)」と「キー値監視」(3)

新しいスレッドを立ててそのスレッドから「通知」をそのまま送ると「スレッド・セーフでない」ので、セレクター使ってちゃんとメインスレッドのNSNotificationCenterから「通知」されるように書く必要あり。


前回からの続き。
前回のを実行したときのログが以下のように出力されたけど、気になる点が下から2行目。

ログの[470:5c03]は[プロセスID:スレッドID]として現れている。
わたしは、この下から2行目の処理がメインスレッドで実行されることを期待していたのだけれど、スレッドIDを見ると何故かしら新スレッドのIDで実行されているのがわかる。

2010-10-18 11:03:50.584 MyNotification[470:207] メインスレッド:処理開始
2010-10-18 11:03:50.589 MyNotification[470:5c03] 新スレッド:処理開始
2010-10-18 11:03:50.589 MyNotification[470:207] メインスレッド:処理終了
2010-10-18 11:03:51.593 MyNotification[470:5c03] 新スレッド:0
2010-10-18 11:03:52.596 MyNotification[470:5c03] 新スレッド:1
2010-10-18 11:03:53.599 MyNotification[470:5c03] 新スレッド:2
2010-10-18 11:03:54.602 MyNotification[470:5c03] 新スレッド:3
2010-10-18 11:03:55.605 MyNotification[470:5c03] 新スレッド:4
2010-10-18 11:03:55.608 MyNotification[470:5c03] 新スレッド:通知を送るよ。通知の名前はowataだよ。
2010-10-18 11:03:55.615 MyNotification[470:5c03] メインスレッド:通知owataを受け取ってowatayoメソッドを実行したよ。
2010-10-18 11:03:55.619 MyNotification[470:5c03] 新スレッド:処理終了

で、少し調べてみたところ、これは「スレッドセーフでない」状態ということらしい。
メインスレッド以外のスレッドから NSNotification で通知する場合 - 24/7 twenty-four seven

で、もう少し調べてみると、
セレクターを渡すことでメインスレッドのpostNotification:を実行させる方法があるようなので、そっちを試してみた。
前回書いた部分の以下のコードを変更する。

/*
* 新スレッド終了するときの通知させる処理
*/
- (void)didMyWork
{
NSNotification *notification;
notification = [NSNotification notificationWithName:@"owata" object:self userInfo:nil];

NSLog(@"新スレッド:通知を送るよ。通知の名前はowataだよ。");
NSNotificationCenter *center;
center = [NSNotificationCenter defaultCenter];

// これだと、
// [center postNotification:notification];
// 新スレッドで「通知」されるので、
//
// performSelectorOnMainThread:withObject:waitUntilDone:
// こっちでメインスレッドから「通知」されるようにする
//
[center performSelectorOnMainThread:@selector(postNotification:) withObject:notification waitUntilDone:NO];
}


2010-10-18 11:06:50.287 MyNotification[531:207] メインスレッド:処理開始
2010-10-18 11:06:50.289 MyNotification[531:5c03] 新スレッド:処理開始
2010-10-18 11:06:50.290 MyNotification[531:207] メインスレッド:処理終了
2010-10-18 11:06:51.291 MyNotification[531:5c03] 新スレッド:0
2010-10-18 11:06:52.292 MyNotification[531:5c03] 新スレッド:1
2010-10-18 11:06:53.293 MyNotification[531:5c03] 新スレッド:2
2010-10-18 11:06:54.295 MyNotification[531:5c03] 新スレッド:3
2010-10-18 11:06:55.296 MyNotification[531:5c03] 新スレッド:4
2010-10-18 11:06:55.297 MyNotification[531:5c03] 新スレッド:通知を送るよ。通知の名前はowataだよ。
2010-10-18 11:06:55.299 MyNotification[531:207] メインスレッド:通知owataを受け取ってowatayoメソッドを実行したよ。
2010-10-18 11:06:55.300 MyNotification[531:5c03] 新スレッド:処理終了

これでオッケーな感じかな。


追記:何となく変な感じがするので次回、サンプルのコードを書き直す。

2010年10月15日金曜日

「Cocoa Notification (NSNotification,NSNotificationCenter)」と「キー値監視」(2)

「Cocoa Notification (NSNotification,NSNotificationCenter)」を実際に試してみることにする。

新規プロジェクトをView-based Applicationを選択して名前をMyNotificationとして作成してます。

新規ファイル追加で、NSObjectを継承したMyNotificationを作成しています。

以下、編集したファイルの中身です。
なるべく簡潔に分かりやすくを心がけたつもり。



// MyNotificationViewController.h

#import <UIKit/UIKit.h>

@interface MyNotificationViewController : UIViewController {
}

@end



// MyNotificationViewController.m

#import "MyNotificationViewController.h"
#import "MyNotification.h"

@implementation MyNotificationViewController

/*
* 通知受け取ったときに呼ばれる処理
*/
- (void)owatayo
{
NSLog(@"メインスレッド:通知owataを受け取ってowatayoメソッドを実行したよ。");
}

- (void)viewDidLoad
{
[super viewDidLoad];

NSLog(@"メインスレッド:処理開始");

// ここでは、通知を受けるObserverをselfとし、通知するSubjectをmyNotificationとする。
// 新しいスレッドをたて、その新スレッドからの通知を受け取れることの確認によって「監視」のテストとする。
MyNotification *myNotification = [[[MyNotification alloc] init] autorelease];

// NSNotificationCenterのインスタンスに、
// addObserver : 通知を受け取るオブジェクト(ここでは自分自身)
// selector : 通知を受けたときに実行するメソッド
// name : 通知される通知名
// object : どのオブジェクトからの通知を受け取るのか指定できる。nilであれば限定しない。
NSNotificationCenter *center;
center = [NSNotificationCenter defaultCenter];
[center addObserver:self selector:@selector(owatayo) name:@"owata" object:myNotification];

// 新しいスレッドをたて、myNotificationのmyWorkメソッドを実行する。
[NSThread detachNewThreadSelector:@selector(myWork) toTarget:myNotification withObject:nil];

NSLog(@"メインスレッド:処理終了");
}

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

- (void)viewDidUnload {
}

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

@end




// MyNotification.h

#import <Foundation/Foundation.h>

@interface MyNotification : NSObject {
}

- (void)myWork;
- (void)didMyWork;

@end



// MyNotification.m

#import "MyNotification.h"

@implementation MyNotification

/*
* 新スレッドでの処理
*/
- (void)myWork
{
NSLog(@"新スレッド:処理開始");
NSAutoreleasePool *pool;
pool = [[NSAutoreleasePool alloc] init];

for (int i=0; i<5; i++) {
[NSThread sleepForTimeInterval:1.0];
NSLog(@"新スレッド:%d", i);
}

[self didMyWork];
[pool release];
NSLog(@"新スレッド:処理終了");
[NSThread exit];
}

/*
* 新スレッド終了するときの通知させる処理
*/
- (void)didMyWork
{
// 通知を受け取る側では以下のようにobserverをNSNotificationCenterに追加している。
// NSNotificationCenter *center;
// center = [NSNotificationCenter defaultCenter];
//[center addObserver:self selector:@selector(owatayo) name:@"owata" object:myNotification];
//
// name:@"owata" と notificationWithName:@"owata" で指定している @"owata" をキーとして通知のやりとりを行う。
// 後は、NSNotification の postNotification: で通知を送れば、勝手に通知を受け取ってくれる。
NSNotification *notification;
notification = [NSNotification notificationWithName:@"owata" object:self userInfo:nil];

NSLog(@"新スレッド:通知を送るよ。通知の名前はowataだよ。");
NSNotificationCenter *center;
center = [NSNotificationCenter defaultCenter];
[center postNotification:notification];
}

@end

こんな感じにログがでるはず。

2010-10-15 18:51:43.333 MyNotification[2310:207] メインスレッド:処理開始
2010-10-15 18:51:43.336 MyNotification[2310:5c03] 新スレッド:処理開始
2010-10-15 18:51:43.336 MyNotification[2310:207] メインスレッド:処理終了
2010-10-15 18:51:44.337 MyNotification[2310:5c03] 新スレッド:0
2010-10-15 18:51:45.338 MyNotification[2310:5c03] 新スレッド:1
2010-10-15 18:51:46.339 MyNotification[2310:5c03] 新スレッド:2
2010-10-15 18:51:47.341 MyNotification[2310:5c03] 新スレッド:3
2010-10-15 18:51:48.342 MyNotification[2310:5c03] 新スレッド:4
2010-10-15 18:51:48.343 MyNotification[2310:5c03] 新スレッド:通知を送るよ。通知の名前はowataだよ。
2010-10-15 18:51:48.344 MyNotification[2310:5c03] メインスレッド:通知owataを受け取ってowatayoメソッドを実行したよ。
2010-10-15 18:51:48.345 MyNotification[2310:5c03] 新スレッド:処理終了


追記:赤文字の部分にちょっと注意が必要そうなので、次の記事でそのこと書く。今回書いたサンプルコードではスレッド・セーフになってなさそう。

あれ?もしかして、NSNotification と NSNotificationCenter 使うと超簡単だったりするんじゃないの?

続く。

「Cocoa Notification (NSNotification,NSNotificationCenter)」と「キー値監視」(1)

Observerパターン

ダイナミックObjective-C「デザインパターンをObjective-Cで - Observer (1)」
ダイナミックObjective-C「デザインパターンをObjective-Cで - Observer (2)」
ダイナミックObjective-C「デザインパターンをObjective-Cで - Observer (3)」

今回書く内容は、上の記事を参考にして「Cocoa Notification」と「キー値監視」による「監視」についてのことを自分なりに噛み砕いて適当に書いている。間違ってたらごめんなさい。

Observerパターンは、「監視」のためのパターンだそうです。
あるオブジェクトを監視して、そのオブジェクトに何らかの状態変化が起きたとき、変化が起こったことを知らせる。

「Cocoa Notification」と「キー値監視」は、その「監視」のための方法です。


Observer = オブザーバー、監視者


Observerパターンに登場するクラスは2つで、監視するクラスと監視されて通知を行うクラスだ。
  • 監視するクラスの方がObserverクラス
  • 監視されるクラスの方がSubjectクラス

例えば、データが更新される毎にグラフの表示が変わるような処理がある場合、
  • グラフ表示が、Observerとしてデータの変化を「監視する」
  • データ自体が、Subjectとして「監視される」側となり変化が起きたら「通知する」

例えば無理やり、あなた(Subject)とあなたの上司(Observer)として考えてみるとすると、
  • 上司は、Observerとして部下の管理をする。
  • あなたは、Subjectとしてなにかあったら上司に報告する。

具体的には、
  • あなたは与えられた仕事が完了したので、終わったことを上司に報告する。
  • あなたは体調が悪くなったので、早退したいことを上司に報告する。

みたいな。

いや~わかりにくいなぁ。



「監視者」がいて、監視される人として「あなた」がいる場合、「監視」として正しいのはどれ?
  1. 「監視者」が「あなた」の状態変化を「監視」し「通知」する。
  2. 「あなた」の状態が変化したら、「あなた」は「監視者」に「通知」する。
  3. 「あなた」の状態が変化したら、「通知」という方法で「監視者」に気づいてもらう。

正解は、
  1. × これは正しいように見えるけど、ちょっと違う。「監視者」が「監視」し「通知」もしてるのが違う。そもそも実は「監視者」は「監視」しない。「あなた」が「通知」する。
  2. ◯ これ正解。
  3. ◯ これも雰囲気はあってるかな。


混乱するね。
じゃぁ「監視」ってあるけど誰が監視してんの?ってことになるんだけど、結局は誰も監視してないんだよね。

だから「通知」を主として考えると、
  • 通知を受け取るクラスをObserverクラス
  • 通知をするクラスをSubjectクラス

って感じに頭で整理しちゃってもいいんじゃないかなとも思うんだけど。ダメかな。



「NSNotification + NSNotificationCenter」のこと
  • NSNotificationCenterのインスタンスは1つしかない。
  • NSNotificationCenterのインスタンスは1つしかないので、扱い方がシンプルであまり迷わなさそう。
  • 1つしかないNSNotificationCenterのインスタンスを使って、ObserverとSubjectをゆるーい感じで繋げて「監視」対応ができるようになっている。
  • 「通知」する側も「通知」を受け取る側も、1つしかないNSNotificationCenterのインスタンスを一緒になかよく使う感じで。


「キー値監視」のこと
  • 「キー値監視」はNSObjectのレベルで実装されている。
  • NSNotificationと比べた場合、「監視者」が「通知」を受けた後に実行するメソッドを指定することが出来ない。
  • キー値コーディングのsetValue:forKey:というメソッドで「通知」しているので、特に何もしなくとも自動的に通知が行われているように感じられる。これは、通知の乱発につながる場合があるらくい。
  • その実際は、自分でアクセサメソッドを実装してその中で手動での制御が必要になるかもしれない。


あーダメだ。ちゃんと理解できてないないない。
ない。

続く。

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] 受信完了

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用のドライバは熟成されてないね。
タップを無効にできるだけでもいいんだけどなぁ。

そんな感じ。

2010年8月31日火曜日

レビューされたい

ぼくのMemohaとMoneyhaがAppBankでレビューされることはないだろうなぁ。
最も問題になるのはアプリのアイコンがイケテないところだろうなぁ。

2010年7月8日 Macbook Pro 買ったった

2010年7月8日にMacbook Pro 15インチをカスタマイズして手に入れた。
ディスプレイを高解像度(1600x900)にして、キーボードをUS仕様に。

訂正:高解像度(1600x900) → 高解像度(1680x1050)ワイドスクリーンディスプレイ(非光沢)

いいよいいよ、Macbook Pro。


MacbookPro* myMacbookPro = [[MacbookPro alloc] init];



2010年8月19日にMacbook ProのHDDをSSDに換装した。
これ、CSSD-S6M64NMQ 。64GBで小さいけどいいの。
Macbook Proは開発用途に利用するの。


[myMacbookPro setHDD:nil];
[myMacbookPro setSSD:64];



MacBook Pro の前は、Late 2006 のMacbook白。
確か2007年4月に購入して、HDDは2回交換してる。
写真と動画と音楽の置き場になり、iPhoneの母艦として使っていきますよ。
MacBook Pro使い始めてからはモッサリ感を感じるようになったけど、MacBookはまだまだ現役です。

2010年8月4日水曜日

Moneyha 1.0.0

2010年8月4日(水)にiPhoneアプリ Moneyha をApp Storeにリリースできました。

Moneyhaの詳細はこんな感じです。
Meoneyhaは、使ったお金を記録するだけのアプリです。
シンプルな家計簿か小遣い帳アプリを作ろうと機能を削っていったら、家計簿はおろか小遣い帳でもなくなってしまいました。
なので、このアプリに多くを求めないでください。

こんな人に使って欲しい。
・余る予定のお小遣いが、何故かしら月末に足らなくなり、その原因がまったく思いつかない人。

こんな人には向いてないです。
・すでに家計簿アプリや小遣い帳アプリを使っている人。
・資産管理したい人。
・予算管理したい人。
・もらったお金も記録したい人。
・グラフを見たい人。
・いろいろと集計したい人。
・締め日を選びたい人。
・計算機機能を求める人。
・他のアプリと連携をとりたい人。
・お金持ちな人

2010年7月29日木曜日

Memoha 1.2.0

MemohaというiPhoneアプリなのですが、Appleの審査を経て今日バージョン1.2.0をリリースできました。
iOS4への対応になります。


現状、Googleドキュメントの機能追加(エディタが新しくなった)の影響でGoogle文書への保存ができない場合があります。
ドキュメント設定から新バージョンを利用しないように設定変更すること回避できますのでGoogleドキュメントの設定変更をお願いします。

2010年7月28日水曜日

Bloggerでプログラムのソースコード扱えるようにする

Blogger Syntax Highliter っていうガジェットを入れて、プログラムのソースコードを表示できるようにする。
「Blogger Syntax Highliter」で調べてガジェット追加すればよい。

投稿画面のエディタで、HTMLの編集を利用して以下のように入れると、


function hello() {
echo "Hello World";
}



こんな感じになる。

function hello() {
echo "Hello World";
}


投稿画面からのプレビューでは反映されないけど気にするな。
デザイン画面で追加したガジェットを一番下に配置して、タイトルを空欄にする。