Using ARC and iOS 6.1, I have a simple class here to demonstrate my problem:
#import <GHUnitIOS/GHUnit.h>
@interface MyClass : NSObject
@property BOOL cancel;
@property BOOL dead;
-(void)doSomething;
-(void)reset;
-(void)logMe;
@end
@implementation MyClass
-(id)init {
self = [super init];
if(self) {
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(reset) name:@"dude" object:nil];
NSLog(@"I'm alive");
}
return self;
}
-(void)dealloc {
_dead = YES;
[[NSNotificationCenter defaultCenter] removeObserver:self];
[MyClass cancelPreviousPerformRequestsWithTarget:self];
NSLog(@"I'm dead");
}
-(void)doSomething {
NSLog(@"dude:%d", _dead);
if(!_cancel) {
[self performSelector:@selector(doSomething) withObject:nil afterDelay:0.2];
NSLog(@"scheduled");
}
[self logMe];
}
-(void)reset {
NSLog(@"reset");
[MyClass cancelPreviousPerformRequestsWithTarget:self];
_cancel = YES;
[self doSomething];
}
-(void)logMe {
NSLog(@"logme");
}
@end
@interface ATest : GHTestCase
@end
@implementation ATest
-(BOOL)shouldRunOnMainThread {return YES;}
-(void)setUpClass {}
-(void)tearDownClass {}
-(void)setUp {}
-(void)tearDown {}
-(void)testBlah {
MyClass* blah = [[MyClass alloc] init];
[blah doSomething];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^(void){
[[NSNotificationCenter defaultCenter] postNotificationName:@"dude" object:nil];
});
blah = nil;
}
@end
In the test, MyClass gets instantiated and I kick off doSomething, which performs some work (i.e., logging) and then calls itself after 0.25s if _cancel is false. Meanwhile, I schedule a notification to fire (which eventually sets _cancel to true) after 1.0s. Then I nil out blah.
So my expectation is the timer that gets created by the performSelector:withObject:withDelay is owning a reference to MyClass.
However, when I run this test with zombies enabled, I get this output:
2013-02-28 15:30:55.518 Tests[11946:c07] ATest/testBlah
2013-02-28 15:30:56.789 Tests[11946:c07] Re-running: ATest/testBlah
2013-02-28 15:30:56.790 Tests[11946:c07] I'm alive
2013-02-28 15:30:56.790 Tests[11946:c07] dude:0
2013-02-28 15:30:56.791 Tests[11946:c07] scheduled
2013-02-28 15:30:56.791 Tests[11946:c07] logme
2013-02-28 15:30:56.792 Tests[11946:c07] ATest/testBlah ✔ 0.00s
2013-02-28 15:30:56.991 Tests[11946:c07] dude:0
2013-02-28 15:30:56.992 Tests[11946:c07] scheduled
2013-02-28 15:30:56.992 Tests[11946:c07] logme
2013-02-28 15:30:57.193 Tests[11946:c07] dude:0
2013-02-28 15:30:57.194 Tests[11946:c07] scheduled
2013-02-28 15:30:57.194 Tests[11946:c07] logme
2013-02-28 15:30:57.395 Tests[11946:c07] dude:0
2013-02-28 15:30:57.395 Tests[11946:c07] scheduled
2013-02-28 15:30:57.396 Tests[11946:c07] logme
2013-02-28 15:30:57.596 Tests[11946:c07] dude:0
2013-02-28 15:30:57.597 Tests[11946:c07] scheduled
2013-02-28 15:30:57.597 Tests[11946:c07] logme
2013-02-28 15:30:57.792 Tests[11946:c07] reset
2013-02-28 15:30:57.793 Tests[11946:c07] I'm dead
2013-02-28 15:30:57.793 Tests[11946:c07] * -[MyClass doSomething]: message sent to deallocated instance 0xb584880
Why is self being deallocated after I call cancelPreviousPerformRequestsWithTarget: in the reset method?
Is this issue an ARC problem or a coding error?