Set Timer on Swift

11,339

Solution 1

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()
    }
}

Solution 2

Set repeats = true , make it self not ().self , I tried this and it works , also you have a mistake of trying to start and stop the timer in init

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

    }
    func stopTimer() {

        timer.invalidate()

    }

    init() {

    }
}
Share:
11,339

Related videos on Youtube

Juan Burolleau
Author by

Juan Burolleau

Updated on June 04, 2022

Comments

  • Juan Burolleau
    Juan Burolleau almost 2 years

    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()
    
  • Juan Burolleau
    Juan Burolleau about 6 years
    You run it on playgrounds? i get this error: error: Execution was interrupted, reason: EXC_BAD_ACCESS (code=1, address=0x0)
  • Umair_UAS
    Umair_UAS over 4 years
    Thanks for the detailed answer bro. It helps a lot.