Search code examples
iosiphoneswifttimernstimer

How to kill NSTimer after entering background and create a new timer after the app is back to active?


So I am building an simple iOS app using Swift. I need to kill my NSTimer after the app entering background, and create a new one after the app is active again.

Initially my solution is to create a timer of NSTimer class in ViewDidLoad() in the main controller file. And this causes a bug in my app.

I guess I need to kill the timer by using applicationDidEnterBackground() in AppDelegate.swift. But I am not exactly sure about how to do it. Should I create the timer class in AppDelegate.swift or in the main controller? I don't know how Swift files share classes.

I have searched online for solutions, but those posts are all too old, the solutions are written in Objective-C.

I am an absolutely beginner. So I hope someone can explain it in Swift.

Here is the code in my main controller TodoTableViewController.swift:

import UIKit
import CoreData
class TodoTableViewController: UITableViewController, NSFetchedResultsControllerDelegate {....
...
...
override func viewDidLoad() {
    super.viewDidLoad()
    var fetchRequest = NSFetchRequest(entityName: "Todo")
    let sortDescriptor = NSSortDescriptor(key: "dayleft", ascending: true)
    fetchRequest.sortDescriptors = [sortDescriptor]
    if let managedObjectContext = (UIApplication.sharedApplication().delegate as AppDelegate).managedObjectContext {
        fetchResultController = NSFetchedResultsController(fetchRequest:fetchRequest, managedObjectContext: managedObjectContext, sectionNameKeyPath: nil, cacheName: nil)
        fetchResultController.delegate = self
        var e: NSError?
        var result = fetchResultController.performFetch(&e)
        todos = fetchResultController.fetchedObjects as [Todo]
        if result != true {
        println(e?.localizedDescription)
        } }
    var timer = NSTimer.scheduledTimerWithTimeInterval(10, target: self, selector:"update", userInfo:nil, repeats: true)
}
...
}

If I am going to invalidate the timer in AppDelegate.swift, how can I refer the timer to the one I created in TodoTableViewController.swift? Or maybe I should put all the code related to timer in AppDelegate.swift?


update

I have tried to use NSNotificationCenter, here is my code.

import UIKit
import CoreData

class TodoTableViewController: UITableViewController, NSFetchedResultsControllerDelegate {
...

var timer = NSTimer.scheduledTimerWithTimeInterval(10, target: self, selector:"viewDidLoad", userInfo:nil, repeats: true)

func update(){
    .....
}

override func viewDidLoad() {
    super.viewDidLoad()
    ...

    let notificationCenter = NSNotificationCenter.defaultCenter()

    notificationCenter.addObserver(self, selector: "didEnterBackground", name: "UIApplicationDidEnterBackgroundNotification", object: UIApplication.sharedApplication())

    notificationCenter.addObserver(self, selector: "didBecomeActive", name: "UIApplicationWillEnterForegroundNotification", object: UIApplication.sharedApplication())   
}


func didEnterBackground() {
    timer.invalidate()
}
func didBecomeActive() {
    var timer = NSTimer.scheduledTimerWithTimeInterval(10, target: self, selector:"update", userInfo:nil, repeats: true)
}

...
}

Since timer is declared in didBecomeActive(), in didEnterBackground() there is an error: does not have a member named "timer". If I declare timer outside didBecomeActive()like the code I posted above, there is an error saying "Extra argument 'selector' in call". I already parse the function update() to the selector. I don't know where this error comes from.


Solution

  • Updated your code and added few comments:

    class TodoTableViewController: UITableViewController, NSFetchedResultsControllerDelegate 
    
       //better to instantiate timer inside viewDidLoad
       var timer: NSTimer!
    
       func update(){ }
    
       override func viewDidLoad() {
          super.viewDidLoad()
          startTimer()
    
          let notificationCenter = NSNotificationCenter.defaultCenter()
    
          //UIApplicationDidEnterBackgroundNotification & UIApplicationWillEnterForegroundNotification shouldn't be quoted
          notificationCenter.addObserver(self, selector: "didEnterBackground", name: UIApplicationDidEnterBackgroundNotification, object: nil)
          notificationCenter.addObserver(self, selector: "didBecomeActive", name: UIApplicationWillEnterForegroundNotification, object: nil)   
       }
    
       func didEnterBackground() {
          self.timer.invalidate()
       }
    
       func didBecomeActive() {
          startTimer()
       }
    
       func startTimer() {
          self.timer = NSTimer.scheduledTimerWithTimeInterval(10, target: self, selector:"update", userInfo:nil, repeats: true)
       }
    }
    

    Also notice that NSTimer strongly keeps it's target. So when closing viewcontroller - timer should be invalidated explicitely. Or memory leak will occur and TodoTableViewController will never be destroyed.