0

I did some test and confused with the result.

NSTimer  *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));

And the result :

2018-05-09 21:57:35.708544+0800 newtest[539:96880] Timer Retain count is 3

2018-05-09 21:57:40.104955+0800 newtest[539:96880] Timer Retain count is 2

why NSTimer retainCount is not zero after invalidate?

Community
  • 1
  • 1
  • If you really want to know, run your code with Instruments and use the Allocations tool. You can find your `NSTimer` object and look at its retain/release history there. But, the obvious question is: why would you care? – Phillip Mills May 09 '18 at 14:27

2 Answers2

2

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.
rob mayoff
  • 375,296
  • 67
  • 796
  • 848
0

Rob's explanation is right.

I just add some more details to prove the scheduledTimerWithTimeInterval:target:selector:userInfo:repeats call return a autorelase object。

In XCode, you can user Product-> Perform Action -> Assemble to view assembly code

bl  _objc_msgSend
mov  x29, x29   ;
bl  _objc_retainAutoreleasedReturnValue; Here you see that the return value is Autoreleased

So, before invalid:

  • auto release => + 1
  • runloop => + 1
  • retain => + 1

Result is 3

After invalid

  • auto release => +1
  • retain => +1

Result is 2

If you know Chinese, you may read my blog to view more details about ARC.

Leo
  • 24,596
  • 11
  • 71
  • 92