I often find myself needing a repeating timer as part of a UIViewController. For example - in my recent app Cosmos Timer on the alert sound selection screen, I wanted to check the mute switch status once a second. Depending on the status, I would show or hide a little warning message.
I initially had this in my viewDidLoad method:
- (void) viewDidLoad;
{
mute = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self
selector:@selector(checkIfMuteIsOn)
userInfo:NULL
repeats:YES];
[mute retain];
}And to clean-up the timer, I had:
- (void) viewDidUnLoad;
{
[super viewDidUnload];
[mute invalidate]; // stop timer
[mute release];
}Seems reasonable, right? Well, no. The view controller’s dealloc method was not being called. Nor was viewDidUnLoad. So every time I accessed this screen, another repeating timer was being created to poll the state of the mute switch.
A quick test confirmed this – removing the NSTimer, caused the view controller’s dealloc method to be called as normal.
Looking closer at the docs for scheduledTimerWithTimeInterval:invocation:repeats: - this line jumped out:
The timer instructs the invocation object to retain its arguments.
Ah, so the timer adds a retain to the view controller. And until the NSTimer is stopped, the view controller will never be dealloc’ed or unloaded. But I was stopping the timer in the unload method, so both the view controller and timer would never be destroyed. A circular reference.
So, what is the best way to add a repeating NSTimer to a UIViewController?
I settled on this:
- (void) viewWillAppear:(BOOL)animated;
{
[super viewWillAppear:animated];
// note: timer retained by target!
mute = [NSTimer scheduledTimerWithTimeInterval:1.0f
target:self
selector:@selector(checkIfMuteIsOn)
userInfo:nil
repeats:YES];
}
- (void) viewWillDisappear:(BOOL)animated;
{
[super viewWillDisappear:animated];
[mute invalidate];
}The timer doesn’t need an explicit retain/release since it is retained by the UIViewController target.
Permanent link to this post: http://xinsight.ca/blog/nstimer-in-a-uiviewcontroller/
Older: Reflections on Porting an iOS app to Android
Newer: Add copy functionality to a custom UIView