views:

566

answers:

2

I'm having problems starting & stopping NSTimers. The docs say that a timer is stopped by [timer invalidate];

I have a timer object declared as such

.h
NSTimer *incrementTimer;
@property (nonatomic, retain) NSTimer *incrementTimer;
.m
@synthesize incrementTimer;
-(void)dealloc {
 [incrementTimer release];
 [super dealloc];
 }

-The usual.

When it's needed, my method does the following:

-(void)setGenCount {
    if(!condition1 && condition2) {
        incrementTimer = [NSTimer scheduledTimerWithTimeInterval: 2.0 
                                                      target: self 
                                                    selector:@selector(incrementBatteryVoltage:) 
                                                    userInfo: nil 
                                                     repeats: YES]; 
    }
}

Everything above works fine. However, once that timer does it's job, I want it to invalidate itself. I invalidate the timer because there is an equal decrement method that could be called and would fight against the incrementTimer if it was still active. (Previously, I noticed that my two timers, if active, were acting on the same ivar by increasing & decreasing the value (a sort of fight)... without crashing) The selector called works as follows:

-(void)incrementBatteryVoltage:(NSTimer *)timer {
    if(battVoltage < 24.0) {
         generatorDisplay.battVoltage += 0.1;
      }
    if(battery1Voltage == 24.0) {
         [timer invalidate];
      }
  }

I have an equal method that Decrements the battery count. (previously mentioned)
Due to my program design: the interface simulates a voltage display. When the "machine" is turned off, I want all the timers invalidated, regardless of what any voltage value is. I'm doing this by checking to see if the timer is valid.

-(void)deEnergizeDisplays {

   if([decrementTimer isValid]) {
        [decrementTimer invalidate];
        decrementTimer = nil;
     }

    if([incrementTimer isValid]) {
       [incrementTimer invalidate];
       incrementTimer = nil;
    }

I'm getting numerous "BAD_ACCESS" crashes. The erroneous line call is always pointing toward my [timer isValid] call. It seems that if the timer is invalidated... the pointer doesn't exist either. I know that the [timer invalidate] message disables the timer and then it is removed from the run loop and then it is released. And my understanding is: it is an autoreleased object per it's naming covention.

My thought are: If I'm sending a retain message, shouldn't the reference still exist? I've tried several combinations, taking away:

timer = nil;

or even instead of:

if([timer isValid])

I tried :

if([timer != nil])

and:

if(timer)

I always get the same crash. Thanks for any help on starting & stopping NSTimers.

+2  A: 

You need to retain the value assigned to incrementTimer in setGenCount. You can do this automatically by using your synthesized property, which is accessed via self.:

self.incrementTimer = [NSTimer scheduledTimerWithTimeInterval: ...
Darren
would that be done with: timer = [[NSTimer scheduledTimerWithTimeInterval: 2.0 target: self selector:@selector(incrementBatteryVoltage:) userInfo: nil repeats: YES] retain];
samfu_1
+1 Good call. I should have seen that bug sooner!
e.James
+3  A: 
e.James
PERFECT. Thanks for the detailed explanation and the alternate way of solving the problem!
samfu_1
“Never retain or release the timer. Use only `invalidate`.” Not retaining it is exactly why the questioner crashed upon trying to send it a message; the timer object ceased to exist when the timer callback invalidated it. If an object owns another, it should retain it; there is no good reason to make an exception for timers.
Peter Hosey
As for the circular reference (timers retain/strongly-reference their targets), solve that by having whatever destroys this object tell it to stop monitoring battery voltages first. In that method, invalidate and let go of the timer. That will open the circle and allow you to destroy the battery-voltage monitor object.
Peter Hosey
@Peter Hosey: I see your point, but I respectfully disagree. In my mind, the timer is owned by the runLoop, and not by the controller that initiated it. Especially in this case, where the only reason for the timer is to fire an event at regular intervals.
e.James
@Peter Hosey: Apple's documentation gives some specific advice on memory management for NSTimers: "Because the run loop maintains the timer, from the perspective of memory management there's typically no need to keep a reference to a timer after you’ve scheduled it."http://developer.apple.com/mac/library/documentation/cocoa/Conceptual/Timers/Articles/usingTimers.html#//apple_ref/doc/uid/20000807
e.James
Yeah, I know what the documentation says. I disagree with it. The timer is owned by the run loop *and* the controller that created the timer. I see no reason why the controller should create the timer, know about the timer, and be involved in the timer's lifetime, but not own it.
Peter Hosey
I took the liberty of continuing this argument in my head, and you'll be happy to hear that my brain has declared you as the winner :) The controller *must* maintain a reference to the timer so that `deEnergizeDisplays` can turn it off, and there is no way for that to work reliably unless the controller retains the timer. The controller *needs* the timer object to be accessible, even if it has been invalidated. Thanks for setting me straight.
e.James
Gentlemen, I greatly appreciate the enlightening discussion. e.James,your previous comment IS the reason that I thought I would need to retain a reference. The documentation is vague and my understanding of NSTimer is amorphous in nature.
samfu_1
@samfu_1: I'm glad you went with your instincts on this one instead of mine!
e.James
e.James - thanks, as always, for having detailed answers... they really helped me figure out NSTimer and get it working as it should (this being my first time and all)
iWasRobbed
You're welcome, and I appreciate you taking the time to post a thank-you note. It's always nice to hear that all this typing was worthwhile `;)`
e.James