Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Set Timer on Swift

Im trying to execute the function pepe() repeated times, i get no errors but it doesnt work.

Here is my code:

public class MyClass {
    var timer = Timer()
    @objc func pepe() -> String {
        let hola = "hola"
        return hola
    }
    func startTimer(){
         let seconds = 1.0
        timer = Timer.scheduledTimer(timeInterval: seconds, target: ().self, selector: #selector(pepe), userInfo: nil, repeats: false)

    }
    func stopTimer() {

        timer.invalidate()

    }

    init() {
        self.startTimer()
        self.stopTimer()
    }
}
var pepe = MyClass()
pepe.stopTimer()
pepe.startTimer()
like image 645
Juan Burolleau Avatar asked Feb 08 '18 13:02

Juan Burolleau


People also ask

How do I invalidate a timer in Swift?

invalidate() Stops the timer from ever firing again and requests its removal from its run loop.

How do I pause a timer in Swift?

Pause/Resume Implementation Swift's Timer class has a method called invalidate(). It will stop the timer, but not reset the current value of seconds. This will be useful for resuming.

What is selector in timer Swift?

selector() is where you'd add in the function that you want it to call every timeInterval you set. In your example it's every second. Do bare in mind that in Swift 4 and above, you need to add @objc before a function if you want to call it in a selector like so: @objc func handleEverySecond() { print("Hello world!")


1 Answers

I would suggest:

  1. Don't instantiate an empty Timer. Consider:

    var timer = Timer()
    

    That is creating a blank timer instance. We don't want to do that. You should instead use:

    weak var timer: Timer?
    

    That achieves a few things:

    • The Timer? syntax says it's an "optional", whose value you will instantiate later.

    • When the Timer is scheduled, the runloop keeps a strong reference to it. So, unlike most other objects, you don't personally need to keep a strong reference to your scheduled timers. And you might want the timer variable to be automatically set to nil when the timer is invalidated. So, the weak qualifier says that when the timer is invalidated, the timer variable will automatically be set to nil.

  2. The pepe method signature is not quite right:

    • It shouldn't return anything;

    • You should probably give it the Timer parameter. It's a good habit to get into. You might not need it here, but it makes the intent of the method more clear and you may eventually find it useful to have that Timer parameter; and

    • I might give it a more descriptive name to avoid any ambiguity; I tend to use names like timerHandler.

  3. There's no point in starting and stopping your timer in init.

  4. That reference to ().self in the target should just be self.

  5. In your playground, you're stopping the timer (that hasn't been scheduled started yet) and then starting it.

    You might also want to stop it after a little time, so you get a chance to see the Timer in action.

  6. But, as general rule, when writing method to start the timer, it is prudent to make sure you hadn't (accidentally) already started it. If you don't do this, and accidentally call startTimer twice, you can end up with multiple timers going at the same time (and worst, having lost references to the earlier timers). One common solution typical solution is to see if there is a already timer, and if so, invalidate it before you create you next timer. This is easily accomplished with the optional chaining pattern:

    func startTimer() {
        timer?.invalidate()   // stops previous timer, if any
    
        // now proceed with scheduling of new timer
    }
    

Thus:

import UIKit

// if doing this in playground, include the following two lines

import PlaygroundSupport
PlaygroundPage.current.needsIndefiniteExecution = true

// MyClass

public class MyClass {
    weak var timer: Timer?

    @objc func timerHandler(_ timer: Timer) {
        let hola = "hola"
        print(">>>> \(hola)")
    }

    func startTimer() {
        timer?.invalidate()   // stops previous timer, if any

        let seconds = 1.0
        timer = Timer.scheduledTimer(timeInterval: seconds, target: self, selector: #selector(timerHandler(_:)), userInfo: nil, repeats: true)
    }

    func stopTimer() {
        timer?.invalidate()
    }
}

var object = MyClass()
object.startTimer()

// stop it 10 seconds later
DispatchQueue.main.asyncAfter(deadline: .now() + 10) {
    object.stopTimer()
}

It should be recognized, though, that you can end up with something akin to a strong reference cycle:

  • The runloop keeps a strong reference to scheduled timers;
  • The selector-based timer keeps strong reference to its target;
  • The MyClass (the target) is what presumably is responsible for eventually invalidating that timer.

As a result, MyClass can't be deallocated until the Timer is invalidated. And, as it stands, you cannot just invalidate the Timer in the deinit of MyClass, because deinit won't get called until the the timer is invalidated.

The net effect is that if you have, for example, this MyClass as a property of your view controller and start the timer and then dismiss the view controller, the timer will keep going and MyClass won't be deallocated.

To solve this, you might want to use closure based timer with [weak self] reference, eliminating the strong reference between the timer and MyClass. You can then also automatically invalidate the timer when the MyClass is deallocated:

public class MyClass {
    weak var timer: Timer?

    deinit {
        timer?.invalidate()
    }

    func timerHandler(_ timer: Timer) {
        let hola = "hola"
        print(">>>> \(hola)")
    }

    func startTimer() {
        timer?.invalidate()   // stops previous timer, if any

        let seconds = 1.0
        timer = Timer.scheduledTimer(withTimeInterval: seconds, repeats: true) { [weak self] timer in
            self?.timerHandler(timer)
        }
    }

    func stopTimer() {
        timer?.invalidate()
    }
}
like image 61
Rob Avatar answered Nov 16 '22 01:11

Rob