views:

967

answers:

6

Hi, I'm still new to programming so excuse me if this is silly. I'm programming a simple game and require multiple timers to send different messages at different intervals, so when creating the game, the following is called:

[self gameTimerValidate];
[self scoreTimerValidate];

- (void) gameTimerValidate
{
gameTimer = [NSTimer scheduledTimerWithTimeInterval:[myGame gIntervalSpeed] target:self selector:@selector(gameTimerInterval:) userInfo:nil repeats:YES];
}

- (void) scoreTimerValidate
{
scoreTimer = [NSTimer scheduledTimerWithTimeInterval:0.02 target:self selector:@selector(scoreTimerInterval:) userInfo:nil repeats:YES];
}

I have the scoreTimer and gameTimer declared in my header file ("NSTimer *gameTimer;"). I invalidate the timers when pausing the game or completing the level, and call the above methods again when resuming the game or entering the next level, respectively.

I spent hours today trying to figure out why pausing the game would crash the application. After doing some debugging I noticed the retain count of gametimer was 0, and for scoretimer it was 2. Of course, I can't invalidate a timer with a retain count of 0, but I'm not sure how that came about.

Is there a specific way I must initialize two different NStimers? I been searching for hours on this to no avail...

+2  A: 

The timers are not reusable. After you invalidate them they are removed from the run loop and their retain count is decremented, resulting in their deallocation the next time through the loop. You'll either have to create new ones or stop invalidating them.

Azeem.Butt
A: 

I appreciate the quick reply. Would it work then if I put "NSTimer *scoreTimer" within the method that creates it?

Jason
A: 

I think you should try to find where you might be doing a [scoreTimer retain], and where you might be invalidating (or releasing) gameTimer more than once (you only need to do the latter, if where you checked the retainCount, was after you had invalidated once). You can't increase the retainCount by calling either of

[self gameTimerValidate];
[self scoreTimerValidate];

more than once. You would leak memory, and have two timers firing at the same interval, but you wouldn't have one of those timers have a higher retainCount because of that.

If those two instance variables were retained properties, and you were setting them using self.gameTimer = ..., then I can see them getting retained an extra time. But the code I see doesn't explain your problem.

Search all instances of those two timers and see what else might be messing with things.

One suggestion, you might want to check for nil, like this:

- (void) gameTimerValidate
{
    if (gameTimer == nil)
        gameTimer = [NSTimer scheduledTimerWithTimeInterval:[myGame gIntervalSpeed] target:self selector:@selector(gameTimerInterval:) userInfo:nil repeats:YES];
}

- (void) scoreTimerValidate
{
    if (scoreTimer == nil)
        scoreTimer = [NSTimer scheduledTimerWithTimeInterval:0.02 target:self selector:@selector(scoreTimerInterval:) userInfo:nil repeats:YES];
}
- (void) invalidateMyTimers {
    [gameTimer invalidate], gameTimer = nil;
    [scoreTimer invalidate], scoreTimer = nil;
}
mahboudz
+1  A: 

NSTimer is a tricky class. It doesn't behave like you expect it to.

Firstly, the timer instances are not finally retained by the objects that initialize them but by IIRC, the NSRunLoop. This means that if you have an object that creates a timer, the timer will continue to be active even if you destroy the object that created it and all other references in your custom code. The timer will keep going along firing off messages and you have no clue where they're coming from.

Secondly, you can't stop/pause and resume a timer. When you invalidate it, it's dead.

I suggest creating a light class that will manage the timers for you so you don't have to keep track of it in the rest of your code. e.g.

@interface SDL_SimpleTimerController : NSObject {
    NSTimer *currentTimer;
    NSTimeInterval theInterval;
    id theTargetObj;
    SEL theSelector;

    BOOL timerIsRunning;
}
@property (nonatomic,retain) NSTimer *currentTimer;
@property NSTimeInterval theInterval;
@property (nonatomic,retain) id theTargetObj;
@property SEL theSelector;
@property BOOL timerIsRunning;

-(SDL_SimpleTimerController *) initWithInterval:(NSTimeInterval)anInterval forTarget:(id)aTargetObj andSelector:(SEL)aSelector;

-(void) startTimer;
-(void) stopTimer;          
@end

@implementation SDL_SimpleTimerController
@synthesize currentTimer;
@synthesize theInterval;
@synthesize theTargetObj;
@synthesize theSelector;
@synthesize timerIsRunning;

-(SDL_SimpleTimerController *) initWithInterval:(NSTimeInterval) anInterval forTarget:(id) aTargetObj andSelector:(SEL) aSelector
{
    self=[super init];
    theInterval=anInterval;
    theTargetObj=aTargetObj;
    theSelector=aSelector;
    timerIsRunning=NO;

    return self;

}// end initWithInterval:   


-(void) startTimer{
    if (currentTimer) { 
     currentTimer=Nil;
    }
    currentTimer=[NSTimer scheduledTimerWithTimeInterval:theInterval  target:theTargetObj selector:theSelector userInfo:Nil repeats:YES];
    timerIsRunning=YES;
}//end startTimer

-(void) stopTimer{
    if (currentTimer) {
     [currentTimer invalidate];
     currentTimer=Nil;
    }
    timerIsRunning=NO;
}// end stopTimer

- (void)dealloc { 
    if (currentTimer) {
     [currentTimer release];
     currentTimer=Nil;
    }
    [theTargetObj release];
    theTargetObj=Nil;
    [super dealloc];
}
TechZen
A: 

Thanks for the replies, after giving it some thought I'm going to go with an approach similar to what TechZen said, and and just keep the timers running with a BOOL variable, and using that variable for checking events like pause and such (ie changing the boolean vs stopping and starting the timers).

(also my first time using this website, still learning the format of where the answers go) thanks again!

Jason
A: 

In reply to TechZen's light class, I think you should not release the target object above as you did not create it (therefore don't own it)

 (void)dealloc { 
if (currentTimer) {
    [currentTimer release];
    currentTimer=Nil;
}
**[theTargetObj release];**
theTargetObj=Nil;
[super dealloc];

}

Jagan