#import "CancelTokenTest.h" #import "CancelTokenSource.h" #import "CancelledToken.h" #import "TestUtil.h" #import "Util.h" @interface OnDealloc : NSObject { @private void (^action)(); } +(OnDealloc*) onDealloc:(void(^)())action; @end @implementation OnDealloc +(OnDealloc*) onDealloc:(void(^)())action { OnDealloc* d = [OnDealloc new]; d->action = [action copy]; return d; } -(void) dealloc { action(); } @end @implementation CancelTokenTest -(void) testCancelTokenSource { CancelTokenSource* s = [CancelTokenSource cancelTokenSource]; id c = [s getToken]; __block int n = 0; [c whenCancelled:^{n += 1;}]; [c whenCancelled:^{n += 1;}]; test(n == 0); [s cancel]; test(n == 2); [c whenCancelled:^{n += 1;}]; test(n == 3); } -(void) testCancelledToken { __block int n = 0; [[CancelledToken cancelledToken] whenCancelled:^{n += 1;}]; test(n == 1); } -(void) testCallbacksDeallocWhenSourceDeallocs { __block bool dealloced = false; __block id c = nil; NSObject* lock = [NSObject new]; [Operation asyncRunOnNewThread:^{ OnDealloc* d = [OnDealloc onDealloc:^{ @synchronized(lock) { dealloced = true; } }]; CancelTokenSource* s = [CancelTokenSource cancelTokenSource]; c = [s getToken]; // hold reference to 'd' in callback [c whenCancelled:^{ if (d != nil) { @synchronized(lock) { // should never run test(false); dealloced = true; } } }]; // s goes out of scope, gets dealloced, cancellation becomes impossible // local reference to d and callback reference should both go away, causing d to be dealloced }]; // spin lock while (true) { @synchronized(lock) { if (dealloced) break; } } test(c != nil); test(![c isAlreadyCancelled]); test(dealloced); } @end