I will talk about NSTimer in this order.

  1. Initial a NSTimer
  2. Invalidate a NSTimer
  3. NSTimer and runloop

Initial

To initial a NSTimer, apple offers three ways.

1
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(id)userInfo repeats:(BOOL)rep;

This method creates a timer and schedules it on the current run loop in default mode. The concept of the run loop is pretty important in NSTimer. I will talk about it later.


1
+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(id)userInfo repeats:(BOOL)rep;

Compared with the first method, this one creates a timer without scheduling it on the run loop. But NSTimer only works when it is on a run loop. So, in order to make it work, we need to add it to a run loop by calling the addTimer:(NSTimer *)timer forMode:(NSRunLoopMode)mode from NSRunloop.


1
- (instancetype)initWithFireDate:(NSDate *)date interval:(NSTimeInterval)ti target:(id)t selector:(SEL)aSelector userInfo:(id)ui repeats:(BOOL)rep;

Like the second method, this one does schedule the timer on a run loop, but it offers a way to create a fire date for the timer. Don’t forget to add the timer to a run loop by addTimer: forMode:.

Difference between three waysDifference between three ways

Invalidate

Before taking about invalidate a NSTimer, let’s us see fire method first.

1
- (void)fire;

This method fires a repeating timer without interrupting its regular firing schedule. If the timer is non-repeating, it is automatically invalidated after firing, even if its scheduled fire date has not arrived.

Even if you didn’t put the timer on the run loop, you can use fire method to cause the receiver’s message to be sent to its target.

For example, with a non-repeated timer, after firing the timer, it will be invalidated. So, you can not fire it again.

A non-repeated timerA non-repeated timer

The NSLog:

NSLogNSLog

Then, with a repeated timer:

A repeated timerA repeated timer

The NSLog:

NSLogNSLog

Now, let’s talk about invalidate.

1
- (void)invalidate;

This method is the only way to remove a timer from an NSRunLoop object. Unlike other objects in OC, it is useless to set a timer to nil and let it be destroyed by ARC. The reason is not only the viewController has a strong reference to the timer, but the run loop also has one. When you set _timer = nil, you only break the reference between viewController and timer.

We can simply check the retain count by:

Check retain countCheck retain count

The NSLog:

NSLogNSLog

So, to destroy a NSTimer, we usually do:

1
2
[_timer invalidate];
_timer = nil;

and do this before the view is destroyed, not in -(void)deallocNSTimer andrunloop

What is the run loop? A run loop is an abstraction that (among other things) provides a mechanism to handle system input sources (sockets, ports, files, keyboard, touch, timers, etc).

NSTimer must be on a run loop, otherwise, it will not work. By default, NSTimer will be scheduled on run loop with NSDefaultRunLoopMode. There are other modes such as UITrackingRunLoopMode. The problem is when you scroll a scrollView, the timer will stop until the scrolling is over, because the the run loop is working on the UITrackingRunLoopMode, all events on NSDefaultRunLoopMode will stop and wait.

To solve this problem, we can manually add a timer on all common modes. Notice that UITrackingRunLoopMode is also in common modes.

1
[[NSRunLoop currentRunLoop] addTimer:_timer forMode:NSRunLoopCommonModes];

This will guarantee that timer will work, no matter what mode the current run loop is.