Using a Repeating NSTimer in a UIViewController

09 Jun 2011

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.


Older: Reflections on Porting an iOS app to Android

Newer: Add copy functionality to a custom UIView


View Comments

Related Posts

Recent Posts