Powered By Blogger

2011年4月5日火曜日

iOSでBlocks(クロージャ)2

●Objective-CのクラスのインスタンスをBlockから使用する場合に注意する事
思いがけずオブジェクトが自己参照してしまい、そのオブジェクトが解放されなくなるケース。

以下の様なインスタンス変数を持つクラスがあるとする。

//

//--- TestClass.h ---
typedef void (^block_t)();

@interface TestClass : NSObject {
@private
NSNumber* num_;
block_t block_;
}

- (id)initWithNumber:(NSNumber*)num;
- (void)doBlock;

@end

//--- TestClass.m ---
@implementation TestClass

- (id)initWithNumber:(NSNumber*)num {
  self = [super init];
  if (self) {
    num_ = num;
  }
  return self;
}

- (void)dealloc {
  num_ = nil;
  Block_release(block_);
  [super dealloc];
}

- (void)doBlock {
  if (block_) return;

  block_ = Block_copy(^{NSLog(@"memberObj : %@", num_);});
 //ここでselfのretain countが1つ増加、自己参照!!
 //num_のretain countは変化なし

  block_();
}

@end
一見問題の無い様に見えるが、実際は自分自身(self)をretainしている(循環参照している)ので、このクラスのオブジェクトを最後に破棄しようとreleaseしてもdeallocが呼ばれない(リーク!!)

//

TestClass* testObj = [[TestClass alloc] initWithNumber:[NSNumber numberWithInt:10]];
  [testObj doBlock];
  [testObj release]; //ここで破棄、しかしdeallocが呼ばれない
●何故循環参照してしまったのか
ブロック内でnum_を参照しているのでnum_の参照カウンタが増加すると思うだろうがそうではない。
オブジェクトのメソッド内でそのオブジェクトのインスタンス変数を参照するときは、明示的に書かなくてもself->変数、と解釈され、ブロックはselfの参照カウンタを増加させてしまう。

●解決方法
オブジェクトのメソッド内のブロック内でselfを参照しない。
上のケースの様にselfを明示してなくても実はselfを参照するケースに注意する。
上のケースの場合

//

# if 0
  block_ = Block_copy(^{NSLog(@"memberObj : %@", num_);}); //ここを
# else
  NSNumber* tmp = num_;
  block_ = Block_copy(^{NSLog(@"memberObj : %@", tmp);});   //こう書き換える
# endif
一旦ローカル変数へコピーする事でブロック内でself->と参照される事を防ぐ。
これでブロックはselfを参照せずにnum_だけを参照する様になる。

●雑感
クラスのメソッド内でBlocksを使うときは毎回こういう冗長なことをしないといけないのだろうか。
一見意図の見えづらいコードだし:(

0 件のコメント:

コメントを投稿