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を作成しています。
  1. //  MyKvoViewController.h  
  2.   
  3. #import <UIKit/UIKit.h>  
  4.   
  5. @class creature;  
  6.   
  7. @interface MyKvoViewController : UIViewController {  
  8.     creature *human;  
  9. }  
  10.   
  11. @property (nonatomic, retain) creature *human;  
  12.   
  13. @end  

  1. //  MyKvoViewController.m  
  2.   
  3. #import "MyKvoViewController.h"  
  4. #import "creature.h"  
  5.   
  6. @implementation MyKvoViewController  
  7.   
  8. @synthesize human;  
  9.   
  10. - (void)viewDidLoad {  
  11.     [super viewDidLoad];  
  12.   
  13.     human = [[creature alloc] init];  
  14.     human.sex = @"male";  
  15.   
  16.     // humanオブジェクトのcommentプロパティの値に変化があれば selfに通知されるようにする。  
  17.     // ここではselfがオブザーバとなる。  
  18.     [human addObserver:self forKeyPath:@"comment" options:(NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld) context:NULL];  
  19.   
  20.     // いろいろな方法でhumanのcommentプロパティの値を変化させてみよう。  
  21.     human.comment = @"I am a man.";  
  22.     [human setComment:@"I'm a male."];  
  23.     [human setValue:@"I'm not sure." forKey:@"comment"];  
  24.     [human setIsHavingSausage:YES];  
  25.     [human setIsHavingSausage:NO];  
  26.       
  27.     // commentのキー値変更のselfに対する通知を停止する。  
  28.     // addObserver に対して removeObserver。使わなくなったキー値監視は消すように。  
  29.     [human removeObserver:self forKeyPath:@"comment"];  
  30.     human.comment = @"I am a man.";  
  31. }  
  32.   
  33. /** 
  34.  * ここで通知を受けとる 
  35.  */  
  36. - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context  
  37. {  
  38.     if ([keyPath isEqual:@"comment"]) {  
  39.         NSLog(@"通知受信 comment:%@", [(creature *)object comment]);  
  40.     }  
  41. }  
  42.   
  43. - (void)didReceiveMemoryWarning {  
  44.     [super didReceiveMemoryWarning];  
  45. }  
  46.   
  47. - (void)viewDidUnload {  
  48. }  
  49.   
  50. - (void)dealloc {  
  51.     [human release];  
  52.     [super dealloc];  
  53. }  
  54.   
  55. @end  

  1. //  creature.h  
  2.   
  3. #import <Foundation/Foundation.h>  
  4.   
  5. @interface creature : NSObject {  
  6.     NSString *comment;  
  7.     NSString *sex;  
  8.     BOOL isHavingSausage;  
  9. }  
  10.   
  11. @property (nonatomic, retain) NSString *comment;  
  12. @property (nonatomic, retain) NSString *sex;  
  13.   
  14. - (void)setIsHavingSausage:(BOOL)isHavingSausage_;  
  15.   
  16. @end  

  1. //  creature.m  
  2.   
  3. #import "creature.h"  
  4.   
  5. @implementation creature  
  6.   
  7. @synthesize comment;  
  8. @synthesize sex;  
  9.   
  10. - (void)setIsHavingSausage:(BOOL)isHavingSausage_  
  11. {  
  12.     if (isHavingSausage_ && [self.sex isEqualToString:@"male"]) {  
  13.         self.comment = @"I am a real man.";  
  14.     } else {  
  15.         self.comment= @"I'm a woman!";  
  16.     }  
  17.       
  18.     isHavingSausage = isHavingSausage_;  
  19. }  
  20.   
  21. - (void)dealloc {  
  22.     [comment release];  
  23.     [sex release];  
  24.     [super dealloc];  
  25. }  
  26.   
  27. @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)」を試しなおす。
説明できることはほとんど前回までと変わらないのでサンプルコードだけ載せる。

  1. //  MyNotificationViewController.h  
  2.   
  3. #import <UIKit/UIKit.h>  
  4.   
  5. @interface MyNotificationViewController : UIViewController {  
  6.  NSInteger count;  
  7.  NSTimer *timer;  
  8. }  
  9.   
  10. @end  

  1. //  MyNotificationViewController.m  
  2.   
  3. #import "MyNotificationViewController.h"  
  4. #import "MyNotification.h"  
  5.   
  6. @implementation MyNotificationViewController  
  7.   
  8. /** 
  9.  * 新しくスレッドをたてて、myNotificationのmyWorkメソッドを実行する。 
  10.  */  
  11. - (void)newThread  
  12. {  
  13.  count++;  
  14.    
  15.  MyNotification *myNotification = [[[MyNotification alloc] init] autorelease];  
  16.  [NSThread detachNewThreadSelector:@selector(myWork:) toTarget:myNotification withObject:[NSString stringWithFormat:@"%d",count]];  
  17.   
  18.  if(count > 2) {  
  19.   [timer invalidate];  
  20.   timer = nil;  
  21.  }  
  22. }  
  23.   
  24. /** 
  25.  * 通知受け取ったときに呼ばれる処理 
  26.  */  
  27. - (void)owatayo:(id)info  
  28. {  
  29.  NSDictionary *userInfo = (NSDictionary *)[info userInfo];  
  30.  NSLog(@"メインスレッド:(no.%@スレッドから)通知owataを受け取りowatayoメソッドを実行。", [userInfo objectForKey:@"threadCount"]);  
  31. }  
  32.   
  33. - (void)viewDidLoad  
  34. {  
  35.     [super viewDidLoad];  
  36.   
  37.  NSLog(@"メインスレッド:処理開始");  
  38.    
  39.  // NSNotificationCenterのインスタンスに、  
  40.  // addObserver : 通知を受け取るオブジェクト(ここでは自分自身)  
  41.  // selector : 通知を受けたときに実行するメソッド  
  42.  // name : 通知される通知名  
  43.  // object : どのオブジェクトからの通知を受け取るのか指定できる。nilであれば限定しない。  
  44.  NSNotificationCenter *center;  
  45.  center = [NSNotificationCenter defaultCenter];  
  46.  [center addObserver:self selector:@selector(owatayo:) name:@"owata" object:nil];  
  47.    
  48.   
  49.  NSLog(@"新スレッドを0.5秒間隔で作成します。");  
  50.  count = 0;  
  51.  timer = [NSTimer scheduledTimerWithTimeInterval:1.0f target:self selector:@selector(newThread) userInfo:nil repeats:YES];  
  52.   
  53.  NSLog(@"メインスレッド:処理終了");  
  54. }  
  55.   
  56. - (void)didReceiveMemoryWarning {  
  57.     [super didReceiveMemoryWarning];  
  58. }  
  59.   
  60. - (void)viewDidUnload {  
  61. }  
  62.   
  63. - (void)dealloc {  
  64.     [super dealloc];  
  65. }  
  66.   
  67. @end  

  1. //  MyNotification.h  
  2.   
  3. #import <Foundation/Foundation.h>  
  4.   
  5. @interface MyNotification : NSObject {  
  6. }  
  7.   
  8. - (void)myWork:(NSString *)num;  
  9. - (void)didMyWork:(NSString *)threadCount;  
  10.   
  11. @end  

  1. //  MyNotification.m  
  2.   
  3. #import "MyNotification.h"  
  4.   
  5. @implementation MyNotification  
  6.   
  7. /* 
  8.  * 新スレッドでの処理 
  9.  */  
  10. - (void)myWork:(NSString *)threadCount  
  11. {  
  12.  NSLog(@"(no.%@)新スレッドの処理開始", threadCount);  
  13.   
  14.  NSAutoreleasePool *pool;  
  15.  pool = [[NSAutoreleasePool alloc] init];  
  16.  [NSThread sleepForTimeInterval:3.0];  
  17.  [self didMyWork:threadCount];  
  18.  [NSThread sleepForTimeInterval:3.0];  
  19.  [pool release];  
  20.   
  21.  NSLog(@"(no.%@)スレッド破棄", threadCount);  
  22.  [NSThread exit];  
  23. }  
  24.   
  25. /* 
  26.  * 新スレッド終了するときの通知させる処理 
  27.  */  
  28. - (void)didMyWork:(NSString *)threadCount  
  29. {  
  30.  NSDictionary *myInfo = [NSDictionary dictionaryWithObjectsAndKeys:threadCount, @"threadCount", nil];  
  31.   
  32.  // 通知する内容を指定  
  33.  NSNotification *notification;  
  34.  notification = [NSNotification notificationWithName:@"owata" object:self userInfo:myInfo];   
  35.   
  36.  // 通知する(メインスレッドから通知する)  
  37.  NSNotificationCenter *center= [NSNotificationCenter defaultCenter];  
  38.  [center performSelectorOnMainThread:@selector(postNotification:) withObject:notification waitUntilDone:NO];  
  39. }  
  40.   
  41. @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:を実行させる方法があるようなので、そっちを試してみた。
前回書いた部分の以下のコードを変更する。
  1. /* 
  2.  * 新スレッド終了するときの通知させる処理 
  3.  */  
  4. - (void)didMyWork  
  5. {  
  6.  NSNotification *notification;  
  7.  notification = [NSNotification notificationWithName:@"owata" object:self userInfo:nil];  
  8.    
  9.  NSLog(@"新スレッド:通知を送るよ。通知の名前はowataだよ。");  
  10.  NSNotificationCenter *center;  
  11.  center = [NSNotificationCenter defaultCenter];  
  12.   
  13.  // これだと、  
  14.  // [center postNotification:notification];  
  15.  // 新スレッドで「通知」されるので、  
  16.  //   
  17.  // performSelectorOnMainThread:withObject:waitUntilDone:  
  18.  // こっちでメインスレッドから「通知」されるようにする   
  19.  //   
  20.  [center performSelectorOnMainThread:@selector(postNotification:) withObject:notification waitUntilDone:NO];  
  21. }  


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を作成しています。

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


  1. //  MyNotificationViewController.h  
  2.   
  3. #import <UIKit/UIKit.h>  
  4.   
  5. @interface MyNotificationViewController : UIViewController {  
  6. }  
  7.   
  8. @end  


  1. //  MyNotificationViewController.m  
  2.   
  3. #import "MyNotificationViewController.h"  
  4. #import "MyNotification.h"  
  5.   
  6. @implementation MyNotificationViewController  
  7.   
  8. /* 
  9.  * 通知受け取ったときに呼ばれる処理 
  10.  */  
  11. - (void)owatayo  
  12. {  
  13.  NSLog(@"メインスレッド:通知owataを受け取ってowatayoメソッドを実行したよ。");  
  14. }  
  15.   
  16. - (void)viewDidLoad  
  17. {  
  18.     [super viewDidLoad];  
  19.   
  20.  NSLog(@"メインスレッド:処理開始");  
  21.   
  22.  // ここでは、通知を受けるObserverをselfとし、通知するSubjectをmyNotificationとする。  
  23.  // 新しいスレッドをたて、その新スレッドからの通知を受け取れることの確認によって「監視」のテストとする。  
  24.  MyNotification *myNotification = [[[MyNotification alloc] init] autorelease];  
  25.    
  26.  // NSNotificationCenterのインスタンスに、  
  27.  // addObserver : 通知を受け取るオブジェクト(ここでは自分自身)  
  28.  // selector : 通知を受けたときに実行するメソッド  
  29.  // name : 通知される通知名  
  30.  // object : どのオブジェクトからの通知を受け取るのか指定できる。nilであれば限定しない。  
  31.  NSNotificationCenter *center;  
  32.  center = [NSNotificationCenter defaultCenter];  
  33.  [center addObserver:self selector:@selector(owatayo) name:@"owata" object:myNotification];  
  34.    
  35.  // 新しいスレッドをたて、myNotificationのmyWorkメソッドを実行する。  
  36.  [NSThread detachNewThreadSelector:@selector(myWork) toTarget:myNotification withObject:nil];  
  37.   
  38.  NSLog(@"メインスレッド:処理終了");  
  39. }  
  40.   
  41. - (void)didReceiveMemoryWarning {  
  42.     [super didReceiveMemoryWarning];  
  43. }  
  44.   
  45. - (void)viewDidUnload {  
  46. }  
  47.   
  48. - (void)dealloc {  
  49.     [super dealloc];  
  50. }  
  51.   
  52. @end  


  1. //  MyNotification.h  
  2.   
  3. #import <Foundation/Foundation.h>  
  4.   
  5. @interface MyNotification : NSObject {  
  6. }  
  7.   
  8. - (void)myWork;  
  9. - (void)didMyWork;  
  10.   
  11. @end  


  1. //  MyNotification.m  
  2.   
  3. #import "MyNotification.h"  
  4.   
  5. @implementation MyNotification  
  6.   
  7. /* 
  8.  * 新スレッドでの処理 
  9.  */  
  10. - (void)myWork  
  11. {  
  12.  NSLog(@"新スレッド:処理開始");  
  13.  NSAutoreleasePool *pool;  
  14.  pool = [[NSAutoreleasePool alloc] init];  
  15.    
  16.  for (int i=0; i<5; i++) {  
  17.   [NSThread sleepForTimeInterval:1.0];  
  18.   NSLog(@"新スレッド:%d", i);  
  19.  }  
  20.    
  21.  [self didMyWork];  
  22.  [pool release];  
  23.  NSLog(@"新スレッド:処理終了");  
  24.  [NSThread exit];  
  25. }  
  26.   
  27. /* 
  28.  * 新スレッド終了するときの通知させる処理 
  29.  */  
  30. - (void)didMyWork  
  31. {  
  32.  // 通知を受け取る側では以下のようにobserverをNSNotificationCenterに追加している。  
  33.  // NSNotificationCenter *center;  
  34.  // center = [NSNotificationCenter defaultCenter];  
  35.  //[center addObserver:self selector:@selector(owatayo) name:@"owata" object:myNotification];  
  36.  //   
  37.  // name:@"owata" と notificationWithName:@"owata" で指定している @"owata" をキーとして通知のやりとりを行う。  
  38.  // 後は、NSNotification の postNotification: で通知を送れば、勝手に通知を受け取ってくれる。  
  39.  NSNotification *notification;  
  40.  notification = [NSNotification notificationWithName:@"owata" object:self userInfo:nil];  
  41.    
  42.  NSLog(@"新スレッド:通知を送るよ。通知の名前はowataだよ。");  
  43.  NSNotificationCenter *center;  
  44.  center = [NSNotificationCenter defaultCenter];  
  45.  [center postNotification:notification];  
  46. }  
  47.   
  48. @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: を作ってこっちで初期化処理をすることにする。
  1. //  RSSImporter.h  
  2.   
  3. // これ追加  
  4. - (id)initWithRequest:(NSURLRequest *)requestURL;  

  1. // Rssimporter.m  
  2.   
  3. //  
  4. // これ追加  
  5. //  
  6. // - (id)init { // これ削除  
  7. - (id)initWithRequest:(NSURLRequest *)requestURL {  
  8.       
  9.     self = [super init];  
  10.     if (self != nil) {  
  11.   
  12.         self.isBuffering = NO;  
  13.         self.characterBuffer = [NSMutableData data];  
  14.           
  15.         // パーサ用のコンテクストを作る  
  16.         if (!context) {  
  17.             context = xmlCreatePushParserCtxt(&simpleSAXHandlerStruct, self, NULL, 0, NULL);  
  18.         }  
  19.           
  20.   
  21.         // これ削除  
  22.         //NSString *feedURL = @"http://eyesrobe.blogspot.com/feeds/posts/default?alt=rss";  
  23.         //NSURLRequest *requestURL = [NSURLRequest requestWithURL:[NSURL URLWithString:feedURL]];  
  24.   
  25.         NSURLConnection *urlConnection = [[[NSURLConnection alloc] initWithRequest:requestURL delegate:self] autorelease];  
  26.         if (urlConnection == nil) {  
  27.             NSLog(@"error");  
  28.         }  
  29.     }  
  30.       
  31.     return self;  
  32. }  

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

こんな感じで。
  1. // {YourProject}AppDelegate.m   
  2.   
  3. @interface FooAppDelegate : NSObject <uiapplicationdelegate> {  
  4.     UIWindow *window;  
  5.     FooViewController *viewController;  
  6.   
  7.     // これ削除  
  8.     // RSSImporter *importer;  
  9. }  
  10. </uiapplicationdelegate>  

  1. // {YourProject}AppDelegate.h   
  2.   
  3. // これ削除  
  4. // @synthesize importer;  
  5.   
  6.   
  7. - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {      
  8.       
  9.     // これを削除  
  10.     // importer = [[RSSImporter alloc] init];  
  11.   
  12.     // これ追加  
  13.     NSString *feedURL1 = @"http://eyesrobe.blogspot.com/feeds/posts/default?alt=rss";  
  14.     NSString *feedURL2 = @"http://blog.eyesrobe.com/feed/rss";  
  15.     NSArray *feeds = [NSArray arrayWithObjects:feedURL1, feedURL2, feedURL1, feedURL2, feedURL1, feedURL2, feedURL1, feedURL2, nil];  
  16.     for (NSString *feedURL in feeds) {  
  17.   
  18.         NSURLRequest *requestURL = [NSURLRequest requestWithURL:[NSURL URLWithString:feedURL]];  
  19.           
  20.         RSSImporter *importer;  
  21.         importer = [[RSSImporter alloc] initWithRequest:requestURL];  
  22.         [importer autorelease];  
  23.     }  
  24.   
  25.       
  26.     [window addSubview:viewController.view];  
  27.     [window makeKeyAndVisible];  
  28.     return YES;  
  29. }  
  30.   
  31.   
  32. - (void)dealloc {  
  33.     // これ削除  
  34.     // [importer release];  
  35.       
  36.     [viewController release];  
  37.     [window release];  
  38.     [super dealloc];  
  39. }  


たくさんの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 を指定するだけでいいのかな。
  1. 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.のタグの終わりを判断するところ。
ここでバッファに貯めたタグ間の文字列を取得するためにゴニョゴニョしてる。

  1. //  RSSImporter.h  
  2.   
  3. #import <Foundation/Foundation.h>  
  4. #import <libxml/tree.h>  
  5.   
  6. @interface RSSImporter : NSObject {  
  7. @private  
  8.  xmlParserCtxtPtr context;  
  9.  // ここ追加  
  10.  BOOL isItem; // 記事情報か  
  11.  BOOL isBuffering; // バッファー中か  
  12.  NSMutableData *characterBuffer; // バッファする箱  
  13. }  
  14.   
  15. // ここ追加  
  16. @property BOOL isItem;  
  17. @property BOOL isBuffering;  
  18.   
  19. - (void)fromC;  
  20. - (void)fromC:(const char *)localname;  
  21.   
  22. // ここ追加  
  23. - (void)appendCharacters:(const char *)characters length:(NSInteger)length;  
  24. - (void)finishAppendCharacters:(NSString *)element;  
  25.   
  26. @end  


  1. // RSSImporter.m の該当箇所を編集すること   
  2.   
  3. //  
  4. // 要素開始の通知を受けたときの処理  
  5. //  
  6. static void startElementSAX(  
  7.        void *context,  
  8.        const xmlChar *localname,  
  9.        const xmlChar *prefix,  
  10.        const xmlChar *URI,  
  11.        int nb_namespaces,  
  12.        const xmlChar **namespaces,  
  13.        int nb_attributes, int nb_defaulted,  
  14.        const xmlChar **atrributes) {  
  15.  // void *context は Objective-C との橋渡し。  
  16.  // xmlCreatePushParserCtxt()の第2引数でselfとして渡されたのが、contextとして渡ってきた。  
  17.  // 型を合わせることで、Objective-Cのオブジェクトとして扱えるようにしている。  
  18.  RSSImporter *paserImporter = (RSSImporter *)context;  
  19.   
  20.  // ここ追加  
  21.  // タグの開始(要素の開始)を判断する。例えば<title>xxx</title>なら<title>を判断してくれる。  
  22.  // バッファし始めることをここで宣言(paserImporter.isBuffering=YES)する。  
  23.  if (paserImporter.isItem == YES) {  
  24.   if (strncmp((const char*)localname, "title"sizeof("title")) == 0) {  
  25.    paserImporter.isBuffering = YES;  
  26.   }  
  27.   else if (strncmp((const char*)localname, "link"sizeof("link")) == 0) {  
  28.    paserImporter.isBuffering = YES;  
  29.   }  
  30.   else if (strncmp((const char*)localname, "description"sizeof("description")) == 0) {  
  31.    //paserImporter.isBuffering = YES; // 今回はコメントにして記事本文は読み込まないようにしとく  
  32.   }  
  33.   else if (strncmp((const char*)localname, "pubDate"sizeof("pubDate")) == 0) {  
  34.    paserImporter.isBuffering = YES;  
  35.   }  
  36.     
  37.  } else {  
  38.   if (strncmp((const char*)localname, "item"sizeof("item")) == 0) {  
  39.    paserImporter.isItem = YES;  
  40.   }  
  41.   else {  
  42.    paserImporter.isItem = NO;  
  43.   }  
  44.  }  
  45. }  
  46.   
  47. //  
  48. // 要素終了の通知を受けたときの処理  
  49. //  
  50. static void endElementSAX(  
  51.         void *context,  
  52.         const xmlChar *localname,  
  53.         const xmlChar *prefix,  
  54.         const xmlChar *URI) {  
  55.  RSSImporter *paserImporter = (RSSImporter *)context;  
  56.    
  57.  // ここ追加  
  58.  // タグの終了(要素の終了)を判断する。例えば<title>xxx</title>なら</title>を判断してくれる。  
  59.  // バッファを終了する。  
  60.  // 例えば<title>あいうえお</title>なら、Objective-C側で溜めた文字列「あいうえお」  
  61.  // を確定させて次のバッファを溜められるようにしている。  
  62.  if (paserImporter.isBuffering == YES) {  
  63.   if (strncmp((const char*)localname, "title"sizeof("title")) == 0) {  
  64.    [paserImporter finishAppendCharacters:@"title"];  
  65.   }  
  66.   else if (strncmp((const char*)localname, "link"sizeof("link")) == 0) {  
  67.    [paserImporter finishAppendCharacters:@"link"];  
  68.   }  
  69.   else if (strncmp((const char*)localname, "description"sizeof("description")) == 0) {  
  70.    [paserImporter finishAppendCharacters:@"description"];  
  71.   }  
  72.   else if (strncmp((const char*)localname, "pubDate"sizeof("pubDate")) == 0) {  
  73.    [paserImporter finishAppendCharacters:@"pubDate"];  
  74.   }  
  75.  }  
  76.  paserImporter.isBuffering = NO;  
  77. }  
  78.   
  79. //  
  80. // 要素間のテキストの通知を受けたときの処理  
  81. //  
  82. static void charactersFoundSAX(  
  83.           void *context,  
  84.           const xmlChar *characters,  
  85.           int length) {  
  86.  RSSImporter *paserImporter = (RSSImporter *)context;  
  87.   
  88.  // ここ追加  
  89.  // 通知で受け取る文字列は、一回だけじゃなくて連続してくる。(短ければ1回で受け取れる)  
  90.  // それらをその都度Objective-C側にバッファを溜めていく。  
  91.  // 例えば<title>あいうえお</title>なら、startElementSAXからendElementSAXの間  
  92.  // で断続して文字列「あいうえお」を取得することになる。  
  93.  if (paserImporter.isBuffering == YES) {  
  94.   [paserImporter appendCharacters:(const char *)characters length:length];  
  95.  }  
  96. }  


  1. // RSSImporter.m の該当箇所を編集すること   
  2.   
  3. //  
  4. // クラスエクステンションを利用することで、  
  5. // プロパティとメソッドへのアクセスをプライベートからに制限できる  
  6. //  
  7. @interface RSSImporter ()  
  8. // これ追加  
  9. @property (nonatomic, retain) NSMutableData *characterBuffer;  
  10. @end  



  1. // RSSImporter.m の該当箇所を編集すること   
  2.   
  3. @implementation RSSImporter  
  4.   
  5. // ここ追加  
  6. @synthesize isItem; // 記事情報か  
  7. @synthesize isBuffering; // バッファー中か  
  8. @synthesize characterBuffer; // バッファする箱  
  9.   
  10. /** 
  11.  */  
  12. - (id)init {  
  13.    
  14.  self = [super init];  
  15.  if (self != nil) {  
  16.   
  17.   // ここ追加  
  18.   self.isBuffering = NO;  
  19.   self.characterBuffer = [NSMutableData data];  
  20.     
  21.   // パーサ用のコンテクストを作る  
  22.   if (!context) {  
  23.    context = xmlCreatePushParserCtxt(&simpleSAXHandlerStruct, self, NULL, 0, NULL);  
  24.   }  
  25.     
  26.   NSString *feedURL = @"http://eyesrobe.blogspot.com/feeds/posts/default?alt=rss";  
  27.   NSURLRequest *requestURL = [NSURLRequest requestWithURL:[NSURL URLWithString:feedURL]];  
  28.   NSURLConnection *urlConnection = [[[NSURLConnection alloc] initWithRequest:requestURL delegate:self] autorelease];  
  29.   if (urlConnection == nil) {  
  30.    NSLog(@"error");  
  31.   }  
  32.  }  
  33.    
  34.  return self;  
  35. }  
  36.   
  37. /** 
  38.  ここ追加 
  39.  パースされて送られてきた文字を、バッファに溜める。 
  40.  NSMutableDataなcharacterBufferに送られてきた文字をがんがん入れていく。 
  41.  Cの関数から呼ばれる不思議 
  42.  */  
  43. - (void)appendCharacters:(const char *)characters length:(NSInteger)length {  
  44.  [characterBuffer appendBytes:characters length:length];  
  45. }  
  46.   
  47. /** 
  48.  ここ追加 
  49.  溜めたバッファを NSString に変換する 
  50.  Cの関数から呼ばれる不思議 
  51.  */  
  52. - (void)finishAppendCharacters:(NSString *)element {  
  53.   
  54.  NSString *currentString = [[[NSString alloc] initWithData:self.characterBuffer encoding:NSUTF8StringEncoding] autorelease];  
  55.  NSLog(@"#%@ : %@", element, currentString);  
  56.   [characterBuffer setLength:0];  
  57. }  
  58.   
  59. /** 
  60.  メモリ解放 
  61.  */  
  62. - (void)dealloc {  
  63.  [characterBuffer release]; // ここ追加  
  64.  [super dealloc];  
  65. }  


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

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