Shlomi
Shlomi

Reputation: 367

Swift - Timer doesn't stop

I have the following code:

class firstVC: UIViewController {
    var timer : Timer?

    func scheduledTimerWithTimeInterval(){
        timer = Timer.scheduledTimer(timeInterval: 60, target: self, 
        selector: #selector(self.anotherFunc), userInfo: nil, repeats: 
        true)
    }

    override func viewDidAppear(_ animated: Bool) {
        scheduledTimerWithTimeInterval()
    }
}

I'm trying to stop the timer without success:

func stopTimer() {
    if timer != nil {
        timer?.invalidate()
        timer = nil
    }
}

override func viewDidDisappear(_ animated: Bool) {
    super.viewDidDisappear(animated)
    stopTimer()
}

override func viewWillDisappear(_ animated: Bool) {
    super.viewWillDisappear(animated)
    stopTimer()
}

I even tried to put the stop function in applicationWillResignActive And in applicationDidEnterBackground but it didn't stop:

firstVC().stopTimer()

Your help will be appreciated, thank you.

Upvotes: 6

Views: 8639

Answers (6)

Shockki
Shockki

Reputation: 61

Use this version, which doesn't retain self:

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

And invalidate the timer in deinit:

deinit {
    self.timer?.invalidate()
}

Upvotes: 0

guru
guru

Reputation: 2817

Nothing works for me, at last using self did the trick!!!

 self.atimer?.invalidate()
 self.atimer = Timer()

Upvotes: 1

Shlomi
Shlomi

Reputation: 367

After research I found a solution,
The only thing that worked for me is to create functions in AppDelegate file and call them when needed,
Here is the code, the timerSwitch function:

    func timerSwitch()
    {
        if (timerStatus) {
            checkStateTimer = Timer.scheduledTimer(
                timeInterval: 60, 
                target:self, 
                selector: #selector(self.yourFunction), 
                userInfo: nil, repeats: true)
        } else {
            checkStateTimer?.invalidate()
        }
    }

    func stopTimer()
    {
        timerStatus = false
        timerSwitch()

    }

    func startTimer()
    {
        timerStatus = true
        timerSwitch()

    }

While 'yourFunction' is what you want to execute when the timer starts,
In my case is sending heartbeat.
Then I called the timerSwitch is the following functions in AppDelegate:

    func applicationWillResignActive(_ application: UIApplication) {
        stopTimer()
    }

    func applicationDidEnterBackground(_ application: UIApplication) {
        stopTimer() 
    }

    func applicationDidBecomeActive(_ application: UIApplication) {
        startTimer()
    }

Upvotes: 2

Matt Horrigan
Matt Horrigan

Reputation: 31

As a debugging step, try putting var timer = Timer() in the global space (e.g. at the top of the file below the import statements) to make sure there's only one Timer object being created and referred to. If you have the Timer declaration within a function, you'll make a new timer each time you call that function, which causes you to lose the reference to the old one, and ultimately not stop it.

Upvotes: 0

Duncan C
Duncan C

Reputation: 131418

As others have said, you are creating multiple timers without killing the old one before starting a new one. You need to make sure you stop any current timer before starting a new one.

What I do is to make my timers weak. I then use the code `myTimer?.invalidate() before trying to create a new timer:

class firstVC: UIViewController {
    weak var timer : Timer?

    func scheduledTimerWithTimeInterval(){
        timer?.invalidate()
        timer = Timer.scheduledTimer(timeInterval: 60, target: self, 
        selector: #selector(self.anotherFunc), userInfo: nil, repeats: 
        true)
    }
}

By making your timer weak, the only thing that keeps a strong reference to the timer is the run loop. Then, when you invalidate it, it immediately gets released and set to nil. By using optional chaining to call the invalidate method, it doesn't do anything if it's already nil, and stops it and causes it to go nil if it IS running.

Note that this approach only works if you create your timer in one shot using one of the scheduledTimer() factory methods. If you try to create a timer first and then add it to the run loop, you have to use a strong local variable to create it or it gets released as soon as you create it.

Upvotes: 12

arvidurs
arvidurs

Reputation: 3033

The problem is your timer gets instantiated more than once, so the original timer loses its reference. Add a variable didStartTimer = false. And then in viewDidAppear do a validation, and then call the timer func. That should do it.

like this:

class firstVC: UIViewController {
var timer : Timer?
var didStartTimer = false

func scheduledTimerWithTimeInterval(){
    timer = Timer.scheduledTimer(timeInterval: 60, target: self, 
    selector: #selector(self.anotherFunc), userInfo: nil, repeats: 
    true)
}

override func viewDidAppear(_ animated: Bool) {
            if !didStartTimer {
                 scheduledTimerWithTimeInterval()
                 didStartTime = true
            }

}

Upvotes: 2

Related Questions