NSTimer を Blocks で処理する

2010年8月6日金曜日 | Published in | 0 コメント

このエントリーをはてなブックマークに追加

調子に乗って今度は NSTimer に Blocks を導入してみた。

利用イメージはこんな感じ。
[NSTimer scheduledTimerWithTimeInterval:1.0
   block:^(NSTimer* timer, id userInfo) {
    self.counterLabel.text =
   [NSString stringWithFormat:@"%d", counter++];
   }
 userInfo:nil
 repeats:YES];


実装


NSTimer のカテゴリとして実装した。NSTimer 発火時に呼ばれるメソッドを受け取るインスタンスが必要になるが、今回は NSTimerクラスとした(つまりクラスメソッドを呼び出させる)。このあたりは少々違和感はあるが、一番簡単だと思われるのでそうした。

まずはヘッダから。
typedef void (^TIMER_BLOCK__)(NSTimer*, id);

@interface NSTimer (Extension)

+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)seconds
 block:(TIMER_BLOCK__)block userInfo:(id)userInfo repeats:(BOOL)repeats;
@end
ブロックの定義は何箇所かで使うので typedef で定義してみた。

実装。
#define KEY_BLOCK  @"block"
#define KEY_USERINFO @"userInfo"

+ (void)executeBlock__:(NSTimer*)timer
{
 if (![timer isValid]) {
  return;
  // do nothing
 }

 NSDictionary* context = [timer userInfo];
 TIMER_BLOCK__ block = [context objectForKey:KEY_BLOCK];
 id userInfo = [context objectForKey:KEY_USERINFO];

 block(timer, userInfo);
}

+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)seconds
 block:(TIMER_BLOCK__)block userInfo:(id)userInfo repeats:(BOOL)repeats
{
 NSMutableDictionary* context = [NSMutableDictionary dictionary];

 [context setObject:[[block copy] autorelease]
    forKey:KEY_BLOCK];
 if (userInfo) {
  [context setObject:userInfo forKey:KEY_USERINFO];
 }

 NSTimer* timer = [NSTimer scheduledTimerWithTimeInterval:seconds
    target:self
  selector:@selector(executeBlock__:)
  userInfo:context
  repeats:repeats];
 return timer;
}
ポイントは userInfo を NSDictionary へ入れ替え、そこへ渡ってきた blockを詰めていること。これを発火時(executeBlock__:)に取り出して実行している。block の引数として NSTimer の他、userInfo を渡しているが、これは NSDictionary への入れ替えを行うので -[NSTimer userInfo] が使えないため。

block は copy すると同時に autorelease をかけている。NSTimer のインスタンスが破棄されるタイミングで (NSMutableDictionary*)contextも開放されると思うので、autorelease をかけておけば同時に block も開放されると思われる(多分)。

利用側はこんな感じ。
[NSTimer scheduledTimerWithTimeInterval:1.0
   block:^(NSTimer* timer, id userInfo) {
    self.counterLabel.text =
   [NSString stringWithFormat:@"%d", counter++];
   }
 userInfo:nil
 repeats:YES];

サンプル


NSTimerを使って数字をカウントアップするサンプルを作ってみた。異なる周期を持つ3つのタイマー(1秒、2秒、3秒)を作り、画面上の数字をカウントアップする。

ちゃんと動いているようだ。

コードはこんな感じ。
timer1 = [NSTimer scheduledTimerWithTimeInterval:1.0
   block:^(NSTimer* timer, id userInfo) {
    self.counterLabel1.text =
   [NSString stringWithFormat:@"%d", counter1++];
   }
 userInfo:nil
 repeats:YES];

timer2 = [NSTimer scheduledTimerWithTimeInterval:2.0
   block:^(NSTimer* timer, id userInfo) {
    self.counterLabel2.text =
    [NSString stringWithFormat:@"%d", counter2];
    counter2 += 10;
   }
   userInfo:nil
 repeats:YES];

timer3 = [NSTimer scheduledTimerWithTimeInterval:3.0
   block:^(NSTimer* timer, id userInfo) {
    self.counterLabel3.text =
    [NSString stringWithFormat:@"%d", counter3];
    counter3 += 100;
   }
   userInfo:nil
 repeats:YES];

※ソースコードはGitHubからどうぞ。
NSTimerBlocks at 2010-08-06 from xcatsan's iOS-Sample-Code - GitHub

参考情報


自前で Blocks を実装した他の例など。
Cocoaの日々: 遅延実行を Blocksで記述する
-[NSObject performSelector:afterDelay:] に Blocksを導入してみた。
Cocoaの日々: UIActionSheet を Blocks で処理する
UIActionSheetDelegate を使わず Blocks を使ってボタン押下時の処理を書く。

- - - -
Blocks が使えると、たった一個とはいえメソッドを作らなくて楽。ましてや複数タイマーを使う場合も面倒が無くかなりいい。可読性もよくなる。Blocks便利だ。

(追記) Block内では、self の他、そのBlockのレキシカルスコープで定義された他の auto変数も参照することができる (*)。そう考えると userInfo が不要なので無くすことができる。こんな感じ。
timer1 = [NSTimer scheduledTimerWithTimeInterval:1.0
     block:^(NSTimer* timer) {
      self.counterLabel1.text =
    [NSString stringWithFormat:@"%d", counter1++];
      NSLog(@"%@", array);
     }
   repeats:YES];

次回、検証してみる。


(*) 実行側で copyしてヒープへ移しておく必要はある。

Responses

Leave a Response

人気の投稿(過去 30日間)