Subscribe to
Posts
Comments
NSLog(); Header Image

NSTimers Need – (void)pause:(id)sender;

Consider a Cocoa application I'll call "EggLoop." EggLoop sounds an NSBeep(); every x seconds, where x is set by the user in the application with a slider (from 1 to 999).

This is easily done with a repeating NSTimer. Every x seconds, the NSTimer signals a "beep now" method and resets itself.

Imagine that the user (or the developer) wants to pause the timer. Unfortunately, the developer has to write this himself by keeping track of some number (an integer would be fine for whole seconds). When that int = x, beep and reset the integer to 0. This would require a timer running once per second and triggering a method that does little more than int++ and if(int == x) NSBeep();.

Solution? Apple should add these:

- (BOOL)pause:(id)sender;
- (BOOL)resume:(id)sender;

The BOOL could be used to return a NO if the timer was unable to be resumed or paused (perhaps something else invalidated it in the meantime). This is something I considered after titling this entry, as you can see. 🙂

12 Responses to "NSTimers Need – (void)pause:(id)sender;"

  1. Now, I know I'm getting into hot water by questioning a professional Cocoa developer, but wouldn't it be better to not use an NSTimer for the end event?

    I would imagine that you really only need one NSTimer to repeatedly trigger an `increment` method. That increment method would then know when the limit has been reached, to then trigger the `conclude` method (which, I suppose, could itself be an NSNotification posting so that all objects who needed to know when the cycle had ended could be told).

    Your pause and resume methods would still be a big benefit, but you'd save yourself the hassle of having to calculate your one-off timer.

  2. The timers are kept separately for other reasons than those listed here. In this very simple case, a solution that both updated the progress bar and dinged when it reached the maximum would work.

    I could have written a better example case, I suppose. I rewrote the example above to, hopefully, better illustrate my point. Manual tracking via a method fired every second (or every thousandth of a second, or whatever) should not be necessary.

  3. Adding two methods as you outlined would be useful in many instances. Have you filed a bug report/feature request with Apple?

  4. If you didn't mind restarting the cycle on resume, you could implement pause with a call to NSTimer's setFireDate with a value of NSDate's distantFuture.

    Resume would be implemented with setFireDate to dateWithTimeIntervalSinceNow, passing the timer's own timeInterval.

    These could be implemented in a category on NSTimer.

    If you want to handle preserving the remaining delay, you could do it using the timer's userInfo, which could point to a simple object that would maintain and allow setting a float (basically, a mutable NSNumber class). Then you'd use this to preserve the difference between the current time and the fireDate of the timer in the pause method, and use this value in the call to dateWithTimeIntervalSinceNow in resume.

    Or you could subclass NSTimer and add an ivar there if you didn't want to use the userInfo.

  5. Or, Troy, I could ask Apple to implement it as something that should be included in the class so I don't have to worry about it.

  6. Yes this woud be create. I know this from Delphi and i often use and need this.

  7. I agree that pause and resume might be useful, but I think you still need a better example. The above can be done much more cleanly with -[NSObject performSelector:withObject:afterDelay:]. It also works nicely with your slider since the beep interval can be changed on the fly.

    Assume beepInterval is an instance variable of type NSTimeInterval. (NB- Typed in browser, yadda yadda yadda.)

    - (void)beepPeriodically

    {

    NSBeep();

    [self performSelector:@selector(beepPeriodically)

    withObject:nil

    afterDelay:beepInterval];

    }

    - (void)pause

    {

    [[self class] cancelPreviousPerformRequestsWithTarget:self

    selector:@selector(beepPeriodically)

    object:nil];

    }

    - (void)play

    {

    [self beepPeriodically];

    }

  8. But even if you get Apple to implement these (and if I were them, I'd seriously consider whether the demand for this was enough to warrant such an API addition, given that NSTimer is a pretty dead-simple API right now), you would then not be able to use it in your software unless you were willing to require whatever new version of the OS included this feature.

    Personally, I can't say I've yet had need for such a pair of APIs. But good luck getting them to add it nonetheless.

  9. Well, Troy, that's a rather silly argument. If everyone took your stance on things, an API would never evolve.

  10. This came up, more or less, on cocoadev.com, and it was observed that NSTimer basically already has pause and resume.

    // pause

    [timer setFireDate:[NSDate distantFuture]];

    // resume

    [timer setFireDate:[NSDate date];

    // or if you like,

    [timer setFireDate:[NSDate dateWithTimeIntervalSinceNow:[timer timeInterval]]];

  11. Indeed Ken. Thanks.

  12. Useful tip Ken, thanks.