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:というメソッドで「通知」しているので、特に何もしなくとも自動的に通知が行われているように感じられる。これは、通知の乱発につながる場合があるらくい。
  • その実際は、自分でアクセサメソッドを実装してその中で手動での制御が必要になるかもしれない。


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

続く。