遅延実行を Blocksで記述する

2010年8月3日火曜日 | Published in | 0 コメント

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

数秒後に指定した Blocks を実行するメソッドを作ってみた。利用イメージはこんな感じ。

[self performBlock:^(void) {
  self.label.text = @"DONE-4"; }
 afterDelay:4];


performSelector:withObject:afterDelay:


NSObject には数秒後に指定したメッセージを投げるメソッドが用意されている。
NSObject Class Reference - performSelector:withObject:afterDelay:

これを利用して渡された Blocksを実行するメソッドを用意する。今回は NSObject のカテゴリとして実装してみた。

こんな感じ。
@interface NSObject (Extension)
- (void)performBlock:(void (^)(void))block afterDelay:(NSTimeInterval)delay;
@end

- (void)executeBlock__:(void (^)(void))block
{
 block();
 [block release];
}

- (void)performBlock:(void (^)(void))block afterDelay:(NSTimeInterval)delay
{
 [self performSelector:@selector(executeBlock__:)
      withObject:[block copy]
      afterDelay:delay];
}

performBlock:afterDelay: が呼ばれたら、performSelector:withObject:afterDelay: を使い別途用意したメソッド executeBlock__: を遅延実行する。これだけ。


サンプル


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

UILabelを一つ置いてこれを数秒後とに書き換える。

コードはこんな感じ。
- (void)viewWillAppear:(BOOL)animated {

 [self performBlock:^(void) {self.label.text = @"DONE-1";}
   afterDelay:1];

 [self performBlock:^(void) {self.label.text = @"DONE-2";}
   afterDelay:2];

 [self performBlock:^(void) {self.label.text = @"DONE-3";}
   afterDelay:3];

 [self performBlock:^(void) {self.label.text = @"DONE-4";}
   afterDelay:4];
}
1秒ごとにメッセージを書き換えている。

実行すると1秒毎にメッセージが書き換わる。


補足


Blocks はデフォルトでスタックフレーム上に作られる。また Blocks内でメンバ変数が参照されている場合は self が retain され、なおかつ Blocksオブジェクトでの管理対象となる(self というメンバ変数が用意される。また消滅時に releaseされる)。

performBlock:afterDelay: が呼ばれた時の状態は次の通り。
block オブジェクトに selfというメンバ変数があるのがわかる。また block 自体は 0xbfffd528 といった高アドレスに配置されていることからスタックフレーム上にあることが推測できる。

今回は次のように copy を使い、performSelector:withObject:afterDealy: に渡している。
[self performSelector:@selector(executeBlock__:)
     withObject:[block copy]
     afterDelay:delay];
仮に copy を使わないとどうなるのか。次のようにして、executeBlock__ が呼ばれた時の変数を見てみよう。
[self performSelector:@selector(executeBlock__:)
     withObject:block
     afterDelay:delay];
self が無い。これは Blocksがレキシカルスコープを抜けて開放されてしまった為。performSelector:withObject:afterDelay: は非同期実行を行うため,呼び出し後に結果を待つこと無く次の処理へ進む。すると performBlock:afterDelay: を呼び出した側の viewWillAppear: へ制御が戻り、このメソッドも最後まで実行されて終了する。この時 Blocksがスコープを外れるために開放される。その結果、executeBlock__ が呼ばれた時には self が利用できなくなっている(実際には block自体も開放されているので使えない。今回は単純なサンプルなのでたまたまスタックフレームに残っていた blockが参照できたものと思われる)。

このままでは Blocks内で self が使えない。この場合には Blocksをスタックフレームからヒープへ移動させれば良い。その為に Block_copy() が用意されおり、Cocoaを使う場合には copy が利用できる。copyを使った場合の executeBlock__ 内での状態は次の通り。

self が存在する。また blockのアドレスも以前よりも低アドレスに配置されていることがわかる(なのでスタックフレームに配置されているものではないと推測できる)。


参考情報

Blocks Programming Topics: Introduction
Appleによる Blocksの解説
Programming with C Blocks on Apple Devices
丁寧に書かれていて非常に役に立った。Blocksが関数ポインタを利用していることがわかるとスタックとヒープを意識しなければならないことが理解できる。
C/Objective-C + Blocks でクロージャ - TrashSUITE
こちらも参考になった。

Responses

Leave a Response

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