Our DNA is written in Swift

Timing is Everything

zeke817 asks:

Hey guys just wondering how to put a timer in the appdelegate. I need a timer to keep playing on through multiple views instead of just playing on 1 view. Any help apperciated

Using timers is pretty simple. There are plenty examples around and it’s not difficult to understand. Having said that, I am responding to this question for three reasons:

  1. my posts on my blog have been pretty scarce recently due to lots of programming for customer projects
  2. I think I should at least document how I am using timers so that I can refer people to this post when the question arises again and again.
  3. Explaining a simple thing to somebody else is the best way to train clarity in teaching.

Generally speaking timers are not instantiated but scheduled. The difference is that the SDK/OS takes care of their memory management and we only have to worry about whether or not we want them to fire. So we don’t need to ever release a timer, instead we invalidate it.

Now your choice is to have a timer fire just once or repeatedly. If you want it ongoing then it’s wise to have an instance variable defined in the header for saving the address of the NSTimer instance in so that we can command it to invalidate itself once we no longer want it to go on. It it’s just a one-off thing, then we don’t need an IVAR.

That means adding this to the header’s interface description between the curly brackets, a variable named “timer” which points (asterisk) to an NSTimer instance.

NSTimer *myTimer;

Somewhere in our code we want to start the timer. There are two methods of creating a timer, via invocation or target/selector. The second one is easier to understand so it’s the one I’ve been using. With it you specify the method that you want to be called back on every time the timer fires.

myTimer = [NSTimer scheduledTimerWithTimeInterval:5.0 target:self
                               selector:@selector(timerFired:) userInfo:nil repeats:YES];

That creates a timer, that will fire after 5 seconds and then repeatedly every 5 seconds. When it fires it will call timerFired: in the same instance you scheduled it, due to using self. You could also be passing some user data, but we don’t need that for now, so we pass nil.

Of course we need to provide the timerFired: method as well:

- (void) timerFired:(NSTimer *)timer

If you have an ongoing timer the least you should do is to invalidate it in the class dealloc. If you are invalidating it without destroying the instance you should also set your instance variable to nil because invalidating an already invalidated timer causes an exception. But [nil invalidate] is always ignored by objective-C.

- (void)dealloc
    [myTimer invalidate], myTimer = nil;
    // other releases
    [super dealloc];

Having a pointer to the timer allows you to do a couple of useful things with the instance methods that NSTimer provides. You can fire it manually, you can ask it if it isValid or you can invalidate it. Because internally NSTimer works with dates (more precisely: with a time duration in seconds since a reference date) for when it fires and not durations you can also get fireDate or setFireDate.

Now about userInfo, you can put anything in there that you like to have present as data in the timerFired method. If you’re just scheduling timer events for self, then this is mostly useless as you can access all instance variables anyway. But if you have some calculated data from right before the scheduling that’s not in an IVAR then you can pass it. Or if you are scheduling the receiver of the timerEvent to be a different object then it has much more usefulness.

Timers are a great invention, especially for achieving repeated calling of a given method. Though when you find yourself creating timers with repeats:NO then you might be better of using performSelector:withObject:afterDelay:, I think this is cleaner especially since another developer usually expects timers to be continuous.

Categories: Q&A

Leave a Comment