著作一覧 |
以下のインターフェイスを持つオブジェクトを作った。
@interface foo -(void) doSomething; -(void) abort; @end
doSomethingの中で、後でやる処理が必要となったのでdispach_afterを使った。そこから呼び出し元へ結果を通知する。abortは、その動作を取り消すためのメソッドだ。
最初はあまり考えていなかったので、次のようにした。
@implements Foo BOOL _abortRequest; -(void) doSomething { _abortRequest = NO; ... dispatch_time_t t = dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC); // 1秒後に実行 dispatch_after(t, dispatch_get_main_queue(), ^(void) { if (!_abortRequest) { ... NSNotification* n = [NSNotificcation notificationWithName:@"fooResult" object:nil]; [[NSNotificationCenter defaultCenter] postNotification:n]; } } } -(void)abort { _abortRequest = YES; }
ところが、呼び出して中断するテストコードを書くとうまく行かない。後続のテストに介入される。
NSUInteger _calledCount; -(void)testA { Foo* foo = [[Foo alloc]init]; [foo doSomething]; [foo abort]; } -(void)testB { _calledCount = 0; Foo* foo = [[Foo alloc]init]; [foo doSomething]; for { [[NSRunLoop currentRunLoop] runUnitlDate:.....// 時々状態を眺める }; STAssertEqeuals(_calledCount, 1, @"bad called count:%d", _calledCount); // 2は1ではないになる。 } -(void)notificationCallback:(NSNotification*)notification { _calledCount++; }
ということは、dispatch_afterのブロック内で参照しているselfが元のselfではないのだということに気づくまでに時間がかかったが(あるいは、インスタンス変数がコピーされているのかも知れない)、そういうことだろう。.NET Frameworkのデリゲートが参照するthisは呼び出し時のthisだと思うのだが、実際のところどうなんだっけ?
いずれにしても、selfが元のselfでないなら、元のselfを経由してインスタンス変数を参照するしかない。
@interface Foo() @property BOOL abortRequest; @end @implements Foo @synthesize abortRequest = _abortRequest; -(void) doSomething { _abortRequest = NO; ... dispatch_time_t t = dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC); // 1秒後に実行 __block Foo* __weak wself = self; // ブロック内で参照するために呼び出し時のselfを取り出す。 dispatch_after(t, dispatch_get_main_queue(), ^(void) { if (wself && !wself.abortRequest) { // デアロケートされるとwselfはnilになるから、こうする必要がありそうだ(追記) ... NSNotification* n = [NSNotificcation notificationWithName:@"fooResult" object:nil]; [[NSNotificationCenter defaultCenter] postNotification:n]; } } }
で、解決。
ジェズイットを見習え |