4

What is the correct way to release a NSTimer in my dealloc method ? It was created with the following code ?

-(void)mainTimerLoop {

     mainTimer = [NSTimer scheduledTimerWithTimeInterval:1/10
                                                  target:self 
                                                selector:@selector(gameLoop) 
                                                userInfo:nil
                                                 repeats:YES];
}

Thanks

GuybrushThreepwood
  • 5,598
  • 9
  • 55
  • 113

5 Answers5

16

The way you're doing it, you won't ever hit dealloc. A timer retains its target. In this case, that means the timer has retained you. It will not release you until it is invalidated. Since you created the timer, you must also invalidate it at some point prior to dealloc, because the timer's retain will prevent your object's being dealloced.

You have two options:

  • find another place to invalidate the timer (view goes offscreen, application is terminating, what have you)
  • set something else as the timer's target.

As an example of the latter:

@interface GameLoopTimerTarget : NSObject {
    id owner;  /* not retained! */
}
- (id)initWithOwner:(id)owner;
- (void)timerDidFire:(NSTimer *)t;
@end

@implementation GameLoopTimerTarget
- (id)initWithOwner:(id)owner_ {
    self = [super init];
    if (!self) return nil;

    owner = owner_;
    return self;
}

- (void)timerDidFire:(NSTimer *)t {
#pragma unused (t)
    [owner performSelector:@selector(gameLoop)];
}
@end

/* In your main object… */
/* assume synthesized:
@property (retain, NS_NONATOMIC_IPHONE_ONLY) GameLoopTimer *mainTimerTarget; */
- (void)mainTimerLoop {
    self.mainTimerTarget = [[[GameLoopTimerTarget alloc] initWithOwner:self] autorelease];
    mainTimer = [NSTimer scheduledTimerWithTimeInterval:1.0/10.0 target:self.mainTimerTarget selector:@selector(timerDidFire:) userInfo:nil repeats:YES];
}

- (void)dealloc {
    /* other stuff */
    [timer invalidate], timer = nil;
    [mainTimerTarget release], mainTimerTarget = nil;
    /* more stuff */
    [super dealloc];
}

Notice how the time interval is 1.0/10.0 - this could also be written 0.1, but it cannot be written 1/10, as that division will truncate to 0.0.

Also notice how this breaks the retain cycle:

  • Both you and your timer retain the timer target.
  • You hit dealloc at the normal time.
  • You then invalidate the timer and release the timer target.
  • The timer target is then deallocated.
Jeremy W. Sherman
  • 35,901
  • 5
  • 77
  • 111
2

A valid NSTimer is retained by the run loop, which, if it is repeating, will be forever or until you invalidate it. You shouldn't release it, since, in your example code, you did not explicitly retain it. If you invalidate it, it will no longer be retained by the run loop, and will be autoreleased.

This might be OK for a repeating timer, but is dangerous for a one-shot timer, since it might end being released before you ever access it to see if it's valid and/or try to invalidate it (which would lead to a bad-access app crash). Therefore if you plan on, in any way, looking at a timer variable after it's creation (including to check it, invalidate it and/or release it), it might be a good practice to explicitly retain it somewhere in your app, and then release it and set it to nil after it's invalid and you are done with it.

You can release it and set it to nil in one statement if you declare it as a retain property. Then you can write:

self.timer = nil;
hotpaw2
  • 70,107
  • 14
  • 90
  • 153
0

you have a really good answer about NSTimer here How do I use NSTimer? there they talk about stoping a repeating NSTimer doing

[myTimer invalidate];
Community
  • 1
  • 1
JonLOo
  • 4,929
  • 1
  • 19
  • 27
  • 15
    This is wrong - and it is very bad advice! Memory management with an NSTimer is very peculiar: it retains the target, and doesn't release it until it is invalidated. If he creates a repeating NSTimer with self as the target, therefore, the timer retains self and so dealloc will not be called until *after* the timer is invalidated. So don't say "stop the timer *in* your dealloc method." The trick is to find a place to stop the timer *before* the dealloc method. – matt Nov 21 '11 at 04:18
  • Down-voted for the same reasons as given by matt. Need to invalidate and release a timer outside of dealloc. – theLastNightTrain May 23 '12 at 21:31
  • but this answer does not embed the invalidation in dealloc? who gave you that idea? – Motti Shneor Feb 16 '22 at 10:15
0

I think the best advice here is -

Do not retain the NSTimer instance, and do not release it.

As soon as it is scheduled on an NSRunloop (current runloop in the OP's example, an NSTimer is retained by the runloop until being invalidated, or until the runloop stops.

What you should be doing, is to invalidate your timer at the right time - and on the same thread where you created and scheduled it.

Keep in mind, also, that NSTimer retains its target, and won't let the target "die" before it dies itself. design your code so that you don't have a retain cycle that will prevent the releasing of both your object (holding the timer) and the timer (holding you object).

Motti Shneor
  • 2,095
  • 1
  • 18
  • 24
-3

You don't need to release it because it will be autoreleased. Anything created by a convenience method (i.e. you don't call alloc yourself) is the responsibility of the called function to memory manage, which usually means that it will call autorelease on the object it creates before it returns it.

I would assign the timer to a property with the retain keyword though to make sure it doesn't get deallocated on you. Generally autoreleased objects are deallocated in the event loop if they don't have any retains.

Nimrod
  • 5,168
  • 1
  • 25
  • 39
  • 1
    This is wrong. The timer is retained by the NSRunLoop on which it is scheduled. It is not released until it is invalidated. – matt Nov 21 '11 at 04:20