samfu_1
samfu_1

Reputation: 1160

Problems invalidating & re-creating NSTimer(s)

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.

Upvotes: 3

Views: 4705

Answers (2)

James Eichele
James Eichele

Reputation: 119154

UPDATE: See Darren's answer. The problem is that you are not using your property accessor when setting the timers. Instead of:

incrementTimer = [NSTimer ...

You should have:

self.incrementTimer = [NSTimer ...

The self.propertyName = ... syntax will call your accessor method, and thereby automatically retain the object that you send to it (since your property is set up as retain). Simply calling propertyName = ... does not use the property accessor. You are simply changing the value of your ivar directly.


UPDATE #2: After an enlightening conversation with Peter Hosey (see comments), I have removed my earlier suggestion to "never retain or release" your timer object. I have also completely re-written my earlier code because I think the following is a better approach:

Controller.h:

NSTimer *voltageTimer;
float targetBatteryVoltage;
...
@property (nonatomic, retain) NSTimer *voltageTimer;

Controller.m:

@implementation Controller
@synthesize voltageTimer;

- (void)stopVoltageTimer {
    [voltageTimer invalidate];
    self.voltageTimer = nil;
}

- (void)setTargetBatteryVoltage:(float)target {
    [voltageTimer invalidate];
    targetBatteryVoltage = target;
    self.voltageTimer = [NSTimer scheduledTimerWithTimeInterval: 2.0
                                target: self
                              selector: @selector(updateBatteryVoltage:)
                              userInfo: nil
                               repeats: YES];
}

- (void)updateBatteryVoltage:(NSTimer *)timer {
    const float increment = 0.1;
    if (abs(battVoltage - targetBatteryVoltage) < increment) {
        [timer invalidate];
    }
    else if (battVoltage < targetBatteryVoltage) {
        generatorDisplay.battVoltage += increment;
    }
    else if (battVoltage > targetBatteryVoltage) {
        generatorDisplay.battVoltage -= increment;
    }
}

Now, you can simply set a target battery voltage, and the timer magic will happen behind the scenes:

[self setTargetBatteryVoltage:24.0];

Your power-off method would look as follows:

- (void)deEnergizeDisplays {
    [self stopVoltageTimer];
}

Upvotes: 9

Darren
Darren

Reputation: 25619

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: ...

Upvotes: 3

Related Questions