Alternatives to Timers
If you simply want to send a message at some point in the future, you can do so without using a timer. You can use performSelector:withObject:afterDelay: and related methods to invoke a method directly on another object. Some variants, such as performSelectorOnMainThread:withObject:waitUntilDone:, allow you to invoke the method on a different thread. You can also cancel a delayed message send using cancelPreviousPerformRequestsWithTarget: and related methods.
Creating and Scheduling a Timer
1. There are, broadly speaking, three ways to create a timer: scheduling a timer with the current run loop; creating a timer that you later register with a run loop; and initializing a timer with a given fire date.
2. If you specify the selector for the message directly, the name of the method does not matter but it must have the following signature:
- (void)timerFireMethod:(NSTimer*)theTimer
@interface TimerController : NSObject {
NSTimer *repeatingTimer;
NSTimer *unregisteredTimer;
NSUInteger timerCount;
}
@property (assign) NSTimer *repeatingTimer;
@property (retain) NSTimer *unregisteredTimer;
@property NSUInteger timerCount;
- (IBAction)startOneOffTimer:sender;
- (IBAction)startRepeatingTimer:sender;
- (IBAction)stopRepeatingTimer:sender;
- (IBAction)createUnregisteredTimer:sender;
- (IBAction)startUnregisteredTimer:sender;
- (IBAction)stopUnregisteredTimer:sender;
- (IBAction)startFireDateTimer:sender;
- (void)timerFireMethod:(NSTimer*)theTimer;
- (void)invocationMethod:(NSDate *)date;
- (void)countedTimerFireMethod:(NSTimer*)theTimer;
- (NSDictionary *)userInfo;
@end
- (NSDictionary *)userInfo {
return [NSDictionary dictionaryWithObject:[NSDate date] forKey:@"StartDate"];
}
- (void)targetMethod:(NSTimer*)theTimer {
NSDate *startDate = [[theTimer userInfo] objectForKey:@"StartDate"];
NSLog(@"Timer started on %@", startDate);
}
- (void)invocationMethod:(NSDate *)date {
NSLog(@"Invocation for timer started on %@", date);
}
Scheduled Timers
The following two class methods automatically register the new timer with the current NSRunLoop object in the default mode (NSDefaultRunLoopMode):
- scheduledTimerWithTimeInterval:invocation:repeats:
- scheduledTimerWithTimeInterval:target:selector:userInfo:repeats:
- (IBAction)startOneOffTimer:sender {
[NSTimer scheduledTimerWithTimeInterval:2.0
target:self
selector:@selector(targetMethod:)
userInfo:[self userInfo]
repeats:NO];
}
The timer is automatically fired by the run loop after 2 seconds, and is then removed from the run loop.
- (IBAction)startRepeatingTimer:sender {
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:0.5
target:self selector:@selector(timerFireMethod:)
userInfo:[self userInfo] repeats:YES];
self.repeatingTimer = timer;
}
Stopping a Timer
If you create a non-repeating timer, there is no need to take any further action. It automatically stops itself after it fires. If you create a repeating timer, however, you stop it by sending it an invalidate message. You can also send a non-repeating timer an invalidate message before it fires to prevent it from firing.
- (IBAction)stopRepeatingTimer:sender {
[repeatingTimer invalidate];
self.repeatingTimer = nil;
}
- (IBAction)stopUnregisteredTimer:sender {
[unregisteredTimer invalidate];
self.unregisteredTimer = nil;
}
Memory Management
Because the run loop maintains the timer, from the perspective of memory management there's typically no need to keep a reference to a timer after you’ve scheduled it. Since the timer is passed as an argument when you specify its method as a selector, you can invalidate a repeating timer when appropriate within that method. In many situations, however, you also want the option of invalidating the timer—perhaps even before it starts. In this case, you do need to keep a reference to the timer, so that you can send it an invalidate message whenever appropriate. If you create an unscheduled timer, then you must maintain a strong reference to the timer (in a reference-counted environment, you retain it) so that it is not deallocated before you use it.
Perhaps more importantly, a timer also maintains a strong reference to its target. This means that as long as a timer remains valid (and you otherwise properly abide by memory management rules), its target will not be deallocated. As a corollary, this means that it does not make sense for a timer’s target to try to invalidate the timer in its dealloc or finalize method—neither method will be invoked as long as the timer is valid.