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];
mute = [NSTimer scheduledTimerWithTimeInterval:1.0
target:self
selector:@selector(checkIfMuteIsOn)
userInfo:NULL
repeats:YES];
[mute retain];
}
- (void) viewWillDisappear:(BOOL)animated;
{
[super viewWillDisappear:animated];
[mute invalidate], mute = nil;
[mute release];
}
I don’t really need the [mute retain] and [mute release], since the NSTimer retains the target ‘self’ (i.e. the UIViewController) and won’t be autoreleased until we’re done with it - but it’s easier to just add the retain/release than to explain this. ;)
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