Let’s talk about
NSTimer. It’s a core class that every Apple developer needs to use. And yet, despite that, it’s
still misused regularly. We’ll start with the basics.
NSTimer is relatively easy. You just need to create an
NSTimer object and make sure it’s added to the runloop
you want it to fire on. Of course, it’s a little more difficult than that because Apple provides multiple
ways to go about this task. In addition, Apple also provides two ways to get notified when the timer fires. Let’s
take a closer look at the three ways we have of creating an
These are the easiest of the three ways of creating a
NSTimer. You simply call this method and your timer will be allocated, initialized and scheduled on the current run loop.
These methods will only handle the allocation and initialization of the
NSTimer. You will need to manually add the timer to a runloop.
This method needs to be called on an
NSTimer that you have already called alloc on. You will still need to manually add the timer to a runloop. There is no reason to use this method for new code, unless you just like to call
You should’ve noticed that there are two versions of most of those methods. One version takes an
NSInvocation object, the other takes a target,
a selector and something called
userInfo. If you take a look at the
NSInvocation class, you can see that it’s a wrapper around a target and
a selector, along with the ability to pass arguments as well. Conceptually, you can think of the second version with the target, selector and
as a wrapper around the first that will wrap your target and selector into a
NSInvocation with the
userInfo as the only parameter. There are almost
certainly differences in the actual implementation, but it should behave as if it was implementated that way. You can choose either of them, but
I’ve found that the using the target, selector and
userInfo version is typically the easier way to go.
You might be wondering why you wouldn’t just always have it schedule the timer on the current run loop? Well it’s possible that you are configuring
a timer that needs to be attached to a run loop on a different thread. If you were running on a different thread than the main thread and your timer needed to update UI state, you would want to make sure that the timer ran on the main thread and not the current thread. If you do need to handle this, you will need to call
addTimer:forMode: on the desired
NSRunLoop to schedule it.
One thing you didn’t see as an option above is a blocks based method for timers. Apple hasn’t (as of Nov 2015) added blocks APIs to
NSTimer. Which means that by default,
NSTimers will be a little tougher to use than they probably should be. If you really want a blocks API for
NSTimer, it’s fairly trivial to implement one using a category. There are also many implementations on the web, so go check those out if your’re interested.
So now you’ve created an
NSTimer – now what? If you created a one-shot timer (i.e,
NO) and there is no need for you to cancel the timer later, you are pretty much done once it’s been scheduled on a run loop. You can even ignore the
NSTimer object that was returned to you (if you are using ARC, that is. You are using ARC, right?). If you have a repeating timer or you need the ability to cancel the timer, then you need to keep a reference to the
NSTimer. But how?
This is one of the trickier bits about
NSTimer. The normal Cocoa way would be to put it into a strong reference variable somewhere and then reference it as needed. You might even set it to
nil in your
dealloc. And this is almost certainly always wrong for
If you look at the documentation, you’ll find out that
NSTimer holds a strong reference to the target (either directly or through the
NSInvocation object). If that target happens to be the class that is holding a strong reference to the
NSObject, bad things will happen. You’ve just created a circular reference between the two objects and they will never be released.
“But I need the timer to stick around. I can’t just put it into a weak reference variable and expect it to stay around!” Actually, you can. That’s because once it’s been scheduled on a runloop, the runloop now has a strong reference to it. Which means you can use a weak reference and you’ve now broken the circular reference cycle.
So if you have a weak reference to the
NSTimer, how would you actually go about canceling it? This is where the
invalidate methods comes in. You can call invalidate on a
NSTimer object, and that will cause the runloop that it is scheduled on to release it. Since you have a weak reference, your reference will automatically be set to
nil and nothing will leak.
Also if you are not automatically adding your
NSTimer to your current run loop, you will need a temporary strong reference to hold your
NSTimer object while you configure it. Hopefully this example will make this a bit clearer:
// in your @interface @property(nonatomic, weak, readwrite) NSTimer *myTimer; // in your method where you create the timer NSTimer *tmpTimer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(timerFired:) userInfo:nil repeats:YES]; [[NSRunLoop mainRunLoop] addTimer:tmpTimer forMode:NSDefaultRunLoopMode]; self.myTimer = tmpTimer;
If you didn’t have a strong reference to your created timer before it was added to a run loop, you would quickly find out that you didn’t have a timer object anymore. Without it being added to a runloop, nobody is holding a reference. So you need to make sure you have a strong reference at least until you’ve added it.
One more note about
NSTimer. Under no circumstances should you ever attempt to invalidate your timer in the
dealloc method of the target object. And the reason should be obvious now. The
NSTimer has a strong reference to your target object, which means that dealloc will never be called as long as the
NSTimer is scheduled. You should always invalidate in some other method that can be called before the object is expected to go away. For example, if you have a timer in a
UIViewController subclass, you can use
viewWillDisappear as the place to create and invalidate your timers.
Hopefully you’ve learned a little more about
NSTimer now. It’s a simple class with some subtle corner cases. But with just a little bit of understanding of how it works, you can keep yourself out of those corners.
Nov 20, 2015