NSTimer crashing after app returns to foreground

crash, ios, memory-management, nstimer, objective-c

Solution

You should be invalidating your timers when your app becomes inactive. `-applicationWillResignActive:` is a good place to do that. You can create new timers when the app becomes active again.

An educated guess is that your app is crashing because the timers get killed automatically when the app goes inactive, and then you crash when your app returns to the foreground and tries to access the timers, which are no longer valid.

Problem

This bug has been in my code for days - I've been trying to solve it but to no avail. I've done extensive debugging, on simulator and on a device, logging, Crashlytics throwing up the crash log, and it has to do with my `NSTimer` being sent a message after deallocation. I'll explain further. This `NSTimer` controls my `UIToolbar` label on some view controllers, and it keeps updating the label to show the relative time from when the user refreshed the `UITableView`. Upon load, and with every refresh, it shows `Updated just now` and then if the user doesn't refresh/go to another VC, it keeps updating every 60 seconds. So, after a minute, it would show `Updated 1 minute ago`. The `NSTimer` triggers a method that does the `UILabel` updating. On user refresh (by pulling on the refresh control), this method is invoked manually inside my `getAllItems` (that's why I pass `nil`). My `NSTimer` is defined in my superclass as: ``` @property (nonatomic, weak) NSTimer *updateDateTimer; ``` This is the code that get the items into my tableview: ``` - (void)getAllItems { KILL_TIMER(self.updateDateTimer); self.updateLabel = [Formatters boldLabelWithLabel:self.updateLabel withText:@"Checking for items..." withFont:[UIFont fontWithName:kOpenSansBold size:15.0f]]; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ [self fetchObjectsWithRequest:self.request]; sleep(1); dispatch_async(dispatch_get_main_queue(), ^{ [self.refreshControl performSelector:@selector(endRefreshing) withObject:nil afterDelay:0]; self.updatedDate = [NSDate date]; [self updateTimer:nil]; self.updateDateTimer = [NSTimer scheduledTimerWithTimeInterval:kToolbarUpdateLabelInterval target:self selector:@selector(updateTimer:) userInfo:nil repeats:YES]; }); }); } ``` and `KILL_TIMER(q)` is defined, in my superclass, as: ``` #define KILL_TIMER(q) if (q) {DDLogVerbose(@"KILLING TIMER: %@", q); [q invalidate]; q=nil;} ``` This define is invoked in 2 methods: a) in my `getAllItems` method: so when I (re)load items (by invoking that method in `viewDidLoad` or by user refreshing the table view), I always try to kill the current timer that's active, show the 'checking for items', fetch the items, show 'updated just now' and then schedule the timer to execute every 60 seconds. b) on each viewWillDisappear`, as such: ``` - (void)viewWillDisappear:(BOOL)animated { [super viewWillDisappear:animated]; KILL_TIMER(self.updateDateTimer); } ``` However, if I hit the home button, and then reopen the app, it generally (not always, only if I had switched view controllers) crashes with the following message (with NSZombies enabled): ``` 2013-07-11 17:21:37.576 App[3923:907] *** -[ItemCDTVC setUpdateDateTimer:]: message sent to deallocated instance 0x20bc49f0 ``` I do have an `NSNotification` that invokes my `getAllItems`: ``` // Notification received when user returns to app (refreshes the table view) [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(getAllItems) name:UIApplicationWillEnterForegroundNotification object:nil]; ``` This only happens after I switch view controllers using the menu, leave the app, and reopen (the app does not terminate). It does not crash if the user stays in the app, regardless of how many view controllers they switch between. It's a pretty annoying bug, because although it's very easily reproduce, I don't know how to fix it. Any help is appreciated.

Original source

Related problems