Search code examples
iosswiftswift2nstimer

How to make NSTimer for multiple different controllers? In swift


I make app on page-based.based. I have multiple different controllers. Every controller has NSTimer which turn on when the controller viewDidAppear(). I made that NSTimer turn off when controller is viewDidDisappear(). It works when I slowly slide between controllers but if I fast slide controller some timers don't turn off. I tried several different methods. The first method I post notification to the function which turn off timer but it also works when I slowly slide between controllers. The second method I created public class timer which I used for every controller it doesn't work for me. I don't know how can I make it. Please help me. I also have NSTimer in UIView which is located on UIViewController and I need that when UIViewController is disappear to turn off two timers. How can I make it?

My examples on ViewController

  var timer: NSTimer!
  override func viewDidAppear(animated: Bool) {
        super.viewDidAppear(true)
        notificationCenter.postNotificationName("turnOnMemoryArc", object: nil)
        timer = NSTimer.scheduledTimerWithTimeInterval(1.0, target: self, selector: "showMemory", userInfo: nil, repeats: true)
    }

    override func viewDidDisappear(animated: Bool) {
        super.viewDidDisappear(true)
        if timer != nil {
            timer.invalidate()
        }
        notificationCenter.postNotificationName("turnOffMemoryArc", object: nil)
    }

This code in the view

  override func drawRect(rect: CGRect) {
        notificationCenter.addObserver(self, selector: "turnOnMemoryTimer", name: "turnOnMemoryArc", object: nil)
        notificationCenter.addObserver(self, selector: "turnOffMemoryTimer", name: "turnOffMemoryArc", object: nil)
//        timer = NSTimer.scheduledTimerWithTimeInterval(1.0, target: self, selector: "drawMemoryArc", userInfo: nil, repeats: true)
    }

  func turnOnMemoryTimer() {
        print("memory timer is on")
        timer = NSTimer.scheduledTimerWithTimeInterval(1.0, target: self, selector: "drawMemoryArc", userInfo: nil, repeats: true)
    }

    func turnOffMemoryTimer() {
        if timer != nil {
            timer.invalidate()
        }
        print("Memory timer turned")
    }

The second method

import Foundation

public class GlobalTimer {

    public init() { }
    public var controllerTimer: NSTimer!
    public var viewTimer: NSTimer!

}

ViewController

 override func viewDidAppear(animated: Bool) {
    super.viewDidAppear(true)
    notificationCenter.postNotificationName("turnOnBatteryArc", object: nil)
    if GlobalTimer().controllerTimer != nil {
        GlobalTimer().controllerTimer.invalidate()
        GlobalTimer().controllerTimer = NSTimer.scheduledTimerWithTimeInterval(1.0, target: self, selector: "batteryStateForLabel", userInfo: nil, repeats: true)
    } else {
        GlobalTimer().controllerTimer = NSTimer.scheduledTimerWithTimeInterval(1.0, target: self, selector: "batteryStateForLabel", userInfo: nil, repeats: true)
    }
}

UIView

 func turnOnBatteryTimer() {
        print("turnOnBatteryTimer arc")
        if GlobalTimer().viewTimer != nil {
            GlobalTimer().viewTimer.invalidate()
            GlobalTimer().viewTimer = NSTimer.scheduledTimerWithTimeInterval(1.0, target: self, selector: "drawBatteryArc", userInfo: nil, repeats: true)
        } else {
            GlobalTimer().viewTimer = NSTimer.scheduledTimerWithTimeInterval(1.0, target: self, selector: "drawBatteryArc", userInfo: nil, repeats: true)
        }
    }

Solution

  • The main problem in your code is GlobalTimer(). This creates new GlobalTimer instance every single time you do write GlobalTimer().

    Four different instance in the following piece of code - no shared state.

    if GlobalTimer().viewTimer != nil {
        GlobalTimer().viewTimer.invalidate()
        GlobalTimer().viewTimer = NSTimer.scheduledTimerWithTimeInterval(1.0, target: self, selector: "drawBatteryArc", userInfo: nil, repeats: true)
    } else {
        GlobalTimer().viewTimer = NSTimer.scheduledTimerWithTimeInterval(1.0, target: self, selector: "drawBatteryArc", userInfo: nil, repeats: true)
    }
    

    You can fix it with singleton pattern ...

    public class GlobalTimer {
        public static let sharedInstance = GlobalTimer()
        private init() { }
        public var controllerTimer: NSTimer!
        public var viewTimer: NSTimer!
    }
    

    ... by replacing all GlobalTimer() occurences ...

    if GlobalTimer().viewTimer != nil {
    

    ... with GlobalTimer.sharedInstace ...

    if GlobalTimer.sharedInstance.viewTimer != nil {
    

    Next step is ! removal, which is dangerous and can lead to crash if you don't know what you're doing.

    public class GlobalTimer {
        public static let sharedInstance = GlobalTimer()
        private init() { }                     <-- avoid instantiation outside the file
        public var controllerTimer: NSTimer?   <-- was !
        public var viewTimer: NSTimer?         <-- was !
    }
    

    Remaining step is to replace your code using GlobalTimer ...

    if GlobalTimer().viewTimer != nil {
        GlobalTimer().viewTimer.invalidate()
        GlobalTimer().viewTimer = NSTimer.scheduledTimerWithTimeInterval(1.0, target: self, selector: "drawBatteryArc", userInfo: nil, repeats: true)
    } else {
        GlobalTimer().viewTimer = NSTimer.scheduledTimerWithTimeInterval(1.0, target: self, selector: "drawBatteryArc", userInfo: nil, repeats: true)
    }
    

    ... with ...

    GlobalTimer.sharedInstance.viewTimer?.invalidate()
    GlobalTimer.sharedInstance.viewTimer = NSTimer.scheduled...
    

    First line says - call invalidate() function on viewTimer if viewTimer != nil otherwise nothing happens (similar to nil messaging in ObjC, ...). This allows you to remove the if condition, because it invalidates timer or does nothing.

    Second line just assigns new timer.