It's not really your business to know why the retain count is non-zero. Retain counts are an implementation detail and it's best not to pay attention to them if you can possibly avoid it.
Here's what the documentation of invalidate
says:
The NSRunLoop
object removes its strong reference to the timer, either just before the invalidate method returns or at some later point.
So it's not even guaranteed that the retain count be reduced when invalidate
returns.
Furthermore, it's impossible to ever see an object's retain count be zero. When the retain count would become zero, the object is destroyed instead, and you cannot operate (e.g. call CFGetRetainCount
) on a destroyed object. The object's memory has probably been overwritten so trying to do anything with it is undefined behavior.
However, we can make some guesses about why the retain count is still 2 after invalidate
.
For one, we know that your local timer
variable has a strong reference to the timer, so that accounts for +1 to the retain count.
The scheduledTimerWithTimeInterval:target:selector:userInfo:repeats:
selector does not start with new
or alloc
or copy
, so it probably autoreleases the object. This is probably the other +1 in the retain count; the object is probably in the autorelease pool. See Advanced Memory Management Programming Guide for details.
You could test the autorelease theory like this:
NSTimer *timer;
@autoreleasepool {
timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(runTimer) userInfo:nil repeats:YES];
NSLog(@"Timer Retain count is %ld", CFGetRetainCount((__bridge CFTypeRef)timer));
[timer invalidate];
NSLog(@"Timer Retain count is %ld", CFGetRetainCount((__bridge CFTypeRef)timer));
}
NSLog(@"Timer Retain count is %ld", CFGetRetainCount((__bridge CFTypeRef)timer));
// If the retain count is now 1, the timer was in the autoreleasepool,
// and the local `timer` variable now has the only strong reference to
// the timer.