| 著作一覧 |
以下のインターフェイスを持つオブジェクトを作った。
@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];
}
}
}
で、解決。
ジェズイットを見習え |