5

I am running a mainLoop in Cocoa using an NSTimer set up like this:

        mainLoopTimer = [NSTimer scheduledTimerWithTimeInterval:1.0/fps target:self selector:@selector(mainloop) userInfo:nil repeats:YES];
    [[NSRunLoop currentRunLoop] addTimer:mainLoopTimer forMode:NSEventTrackingRunLoopMode];

At Program startup I set the timeInterval to 0.0 so that the mainloop runs as fast as possible. Anyways, I would like to provide a function to set the framerate(and thus the time interval of the timer) to a specific value at runtime. Unfortunately as far as I know that means that I have to reinitialize the timer since Cocoa does not provide a function like "setTimerInterval" This is what I tried:

    - (void)setFrameRate:(float)aFps
{
    NSLog(@"setFrameRate");
    [mainLoopTimer invalidate];
    mainLoopTimer = nil;

    mainLoopTimer = [NSTimer scheduledTimerWithTimeInterval:1.0/aFps target:self selector:@selector(mainloop) userInfo:nil repeats:YES];
    [[NSRunLoop currentRunLoop] addTimer:mainLoopTimer forMode:NSEventTrackingRunLoopMode];
}

but this throws the following error and stops the mainloop:

2010-06-09 11:14:15.868 myTarget[7313:a0f] setFrameRate 2010-06-09 11:14:15.868 myTarget[7313:a0f] * __NSAutoreleaseNoPool(): Object 0x40cd80 of class __NSCFDate autoreleased with no pool in place - just leaking 2010-06-09 11:14:15.869 myTarget[7313:a0f] * __NSAutoreleaseNoPool(): Object 0x40e700 of class NSCFTimer autoreleased with no pool in place - just leaking 0.614628

I also tried to recreate the timer using the "retain" keyword, but that didn't change anything. Any ideas about how to dynamically change the interval of an NSTimer at runtime?

Thanks!

moka
  • 4,353
  • 2
  • 37
  • 63

3 Answers3

3

I know, this thread is amazing old! But I was in search of the functionality too!

As it turned out, that the NSTimes doesn't provide this feature, I decided to build an own one. It's not the best solution, but in my case it was the only possible.

I defined the variable "BOOL keepOn;" in my header.

-(IBAction)doSomething:(id)sender
{
    // Any action to throw every interval.
}

-(IBAction)switchAutoLoad:(id)sender
{
    if([autoLoad state] == NSOffState)
    {
        NSLog(@"auto-reload on!");

        self performSelector:@selector(fireTimer) withObject:nil afterDelay:0];
        keepOn = YES;
        [autoLoad setState:NSOnState];
    }
    else
    {
        NSLog(@"auto-reload off!");
        keepOn = NO;
        [autoLoad setState:NSOffState];
    }
}

-(void)fireTimer
{
    [self doSomething:nil];

    float theTime = 20; // This is the time interval.

    if(keepOn)
    {
        [self performSelector:@selector(fireTimer) withObject:nil afterDelay:theTime];
    }
}

The (very) bis issue is, that the "timer" is waiting the whole delay, before it can get a new value. You see, it's not responding fast :)) And you can't stop it immediately (have to wait for the delay to finish).

And if you stop it, it's throwing one more action as supposed.

Actually it works for my and is ok for little apps and tools.

Julian F. Weinert
  • 7,474
  • 7
  • 59
  • 107
0

You should not release mainLoopTimer.
When you created it, you received an autoreleased instance, and the runloop is keeping the reference to that instance.
It will be released when you remove it from the loop: if you manually release it, it gets deleted and the runloop tries to access an invalid object.
That's why it crashes.

EDIT: I totally misread and I thought I saw release when it was an invalidate, sorry. The problem seems to be that there is no AutoreleasePool in place. This is usually done in main(): create the autorelease pool as the first thing

NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

and release it just before exiting from main.

Have a look here for Autorelease pools documentation.

garph0
  • 1,700
  • 1
  • 13
  • 16
  • what should I do instead to change the interval? – moka Jun 09 '10 at 09:37
  • I beg your pardon. I misread [mainLoopTimer release]. You are acting correctly with the timer, but it seems to be no AutoReleasePool in place. have a look here: http://developer.apple.com/mac/library/documentation/cocoa/conceptual/MemoryMgmt/Articles/mmAutoreleasePools.html – garph0 Jun 09 '10 at 09:48
  • hmm I allready tried to put an autoreleasepool in place, but it didn't work, but I will try again! – moka Jun 09 '10 at 09:50
  • okay, if I put an autoreleasepool in place, and init the timer like this: mainLoopTimer = [[NSTimer scheduledTimerWithTimeInterval:fps target:self selector:@selector(mainloop) userInfo:nil repeats:YES] autorelease]; it does not throw the leaking error, but the App gets interrupted. Any other ideas? – moka Jun 09 '10 at 09:52
  • no, just GDB: Interrupted, I am pretty sure the problem is that I overWrite timer. isn't there any other way to set the interval? – moka Jun 09 '10 at 09:56
  • Nope. Here is another question on the subject here on SO: http://stackoverflow.com/questions/1582740/restart-nstimer-firing-interval . There must be some other problem. I have no mac here, so I cannot make any test – garph0 Jun 09 '10 at 10:01
  • Well I replaced the Timer with an performSelector call on the mainloop itself and now everything works, eccept that performSelector seems to be inaccurate since it does not reach the FPS I tell it to, and always is a few FPS slower (ie instead of 60 only 57 or instead of 120 only 114), any ideas? – moka Jun 09 '10 at 12:11
-2

You can try un-register previous timer and start new one with different frequency. you can un-register by

[yourOldTimer invalidate];
yourOldTimer = nil;

This might solve the purpose.

Unicorn
  • 2,382
  • 6
  • 29
  • 42