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



続かない。