Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 925
Antimony Avatar asked Dec 13 '14 03:12

Antimony


People also ask

Does NSTimer run in background?

Problem is, NSTimer requires an active run loop which is not always readily available on background queues. The main thread has an active run loop but this defeats the purpose of having our timer run in the background so its a definite no go. So, to get a dedicated background-queue-friendly timer, we use GCD.


2 Answers

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.

like image 146
Mikhail Avatar answered Nov 02 '22 23:11

Mikhail


Answer for Objective-C, but I think it's very similar in Swift.

In ViewDidLoad add two notifications:

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didEnterBackground) name:UIApplicationDidEnterBackgroundNotification object:[UIApplication sharedApplication]];

and

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didBecomeActive) name:UIApplicationWillEnterForegroundNotification object:[UIApplication sharedApplication]];

Then add this to your view controller class.

- (void)didEnterBackground{
[_timer invalidate];
}

- (void)didBecomeActive{
_timer = [NSTimer scheduledTimerWithTimeInterval:10 target:self selector:@selector(update) userInfo:nil repeats:YES];
}
like image 35
Senõr Ganso Avatar answered Nov 03 '22 01:11

Senõr Ganso