13

I've been trying to utilize Timer in Swift and I've simplified it town to the following:

func startTimer () {
    timer = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: #selector(ViewController.test), userInfo: nil, repeats: true)
}
func test () {
    print("FIRED")
}

I would like to call this from another function and have verified the startTimer function works, but the timer doesn't fire. Does this have something to do with the RunLoop? I'm fairly new to coding so any explanation would be appreciated.

Midhun MP
  • 103,496
  • 31
  • 153
  • 200
Oliver Hickman
  • 141
  • 1
  • 1
  • 6
  • Is `ViewController` == `self`? – vadian Dec 23 '16 at 19:28
  • From the code snippet, I assume it should fired, so are sure about `startTimer()` has been called, right? and -obviously- your class called "ViewController" – Ahmad F Dec 23 '16 at 19:28
  • Add a print statement inside startTimer to make sure it is called. – Duncan C Dec 23 '16 at 19:55
  • Note that the signature for a timer function is supposed to be `function(_:Timer)`. (It should take a single parameter - a Timer object) – Duncan C Dec 23 '16 at 19:55
  • @DuncanC Not necessarily. An action without parameter is valid, too. – vadian Dec 23 '16 at 19:57
  • 1
    It works, but the docs say that a timer function should take the timer as a parameter. – Duncan C Dec 23 '16 at 19:59
  • 8
    "Does this have something to do with the RunLoop" ... Yes, if not running on the main thread, it does. The easy solutions are to either dispatch the scheduling of the timer back to the main thread or use a GCD timer. See http://stackoverflow.com/a/38164203/1271826. – Rob Dec 23 '16 at 20:26
  • 2
    @Rob It was a RunLoop thing. Dispatching it worked like a charm! Thanks! – Oliver Hickman Dec 26 '16 at 19:06

1 Answers1

20

Good Practice: In startTimer() check that the timer has not already been created and make the assignment. In stopTimer() check that the timer exists before calling invalidate, and set the timer back to nil.

Also, for your selector make sure you have the @objc prefix. You should be able to get a working timer with the code provided. Happy coding!

class SomeClass {
    var timer: Timer?

    func startTimer() {
        guard timer == nil else { return }
        timer = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: #selector(test), userInfo: nil, repeats: true)
    }

    func stopTimer() {
        guard timer != nil else { return }
        timer?.invalidate()
        timer = nil
    }

    @objc func test() {

    }
}
Raptor
  • 53,206
  • 45
  • 230
  • 366
Alex Blair
  • 584
  • 1
  • 4
  • 19
  • One last thing, I don't think ViewController.test is required in your selector parameter, test should work just fine. – Alex Blair Dec 23 '16 at 22:54
  • 2
    why does checking to see if its nil matter before you set it to nil – R.P. Carson Aug 15 '17 at 20:00
  • It prevents calling invalidate() on a potentially nil object. For instance, lets say you call stopTimer() in multiple functions. It fires once, invalidates the timer, and is set back to nil. If stopTimer() is called again in succession, without reinitializing the timer, you would receive a fatal error. This is because the timer has already been deallocated. Both guard statements are in place to keep starting and stopping the timer in sync. – Alex Blair Aug 22 '17 at 01:08
  • 13
    but it uses optional chaining and if timer is nil, invalidate wont be called – R.P. Carson Aug 22 '17 at 14:41
  • 2
    That's true! @R.P.Carson. – Alex Blair Aug 22 '17 at 16:59
  • What if `startTimer()` has a completion block depending on the `test()` code? – Tom Spee Oct 13 '17 at 11:44
  • Could you be more specific? – Alex Blair Jan 16 '18 at 22:48