8

I'm trying to deinit/invalidate Timer when user press back button but not when he push to next ViewController.

var timer = Timer()
                timer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(timePrinter), userInfo: nil, repeats: true)
                timer.fire()

override func viewWillDisappear(_ animated: Bool) {
    super.viewDidDisappear(animated)
    if self.isMovingFromParentViewController{
        timer.invalidate()
    }
}

It is not working when user press back button.

Nitesh
  • 1,564
  • 2
  • 26
  • 53

2 Answers2

21

When you use a scheduled timer with 'target/selector', it retains its target. More specifically, the Runloop retains scheduled timers, in turn which retain their target.

I use this version, which doesn't retain self:

Timer.scheduledTimer(withTimeInterval: 1, repeats: true, block: { [weak self] _ in
    self?.doSomethingRegularly()
})

And you'll still need to invalidate the above timer in your deinit as well, otherwise you'll leak the timer (but not your class).

Chris
  • 39,719
  • 45
  • 189
  • 235
6

Edit

Martin R raises a good point: The timer will hold a strong reference to the view controller, so my suggestion of putting the timer.invalidate() in the view controller’s deinit() method won’t work.

Chris’ suggestion of using the closure version of Timer will solve that, so you CAN put the timer.invalidate() call in your view controllers’ deinit() method. So you should also rewrite your timer code as in Chris’s answer.

——————————————

Don't put the timer invalidate in viewWillDisappear(_:). Create a deinit method and put it there. When you press the back button the current view controller should be released and the deinit method will fire.

deinit {
  timer.invalidate()
}
Duncan C
  • 128,072
  • 22
  • 173
  • 272
  • 2
    @Nitesh, if `deinit()` is not being fired, then your vc is not being released, which means you are holding strong reference to it. Check your code, especially, closures(make sure you use weak reference to your vc in closures). – Fahri Azimov Feb 03 '17 at 05:26
  • When I comment out Timer() then deinit is getting called but not with Timer – Nitesh Feb 03 '17 at 05:27
  • @Nitesh set timer value nil in "viewwillDisapper" method – Nidhi Patel Feb 03 '17 at 07:10
  • @NidhiPatel Then this would be done when user goes to next page too. I don't want to make it nil at that time. It should be nil only when user hits back button. – Nitesh Feb 03 '17 at 07:11
  • let isBack = Bool(). when click back button isBack value true and set timer value nil. – Nidhi Patel Feb 03 '17 at 07:19
  • 2
    Simply invalidating the time in `deinit` will probably not work. The timer *retains* its target while running, therefore the view controller is not released and deinit not called. – Martin R Feb 03 '17 at 08:56
  • @MartinR it is not getting called. I tried `deinit` after removing my Timer() then `deinit` is getting called. – Nitesh Feb 03 '17 at 08:58
  • @MartinR What apart from `deinit` I have to do ? – Nitesh Feb 03 '17 at 09:22
  • I am on Swift 5 and getting a warning saying "Cannot access property 'timer' here in deinitializer; this is an error in Swift 6" Is this no longer required in the later version of Swift? Are timers automatically invalidated on deinit? – Bocaxica Oct 20 '22 at 14:10