Set Timer on Swift
Solution 1
I would suggest:
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 thetimer
variable to be automatically set tonil
when the timer is invalidated. So, theweak
qualifier says that when the timer is invalidated, thetimer
variable will automatically be set tonil
.
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 thatTimer
parameter; andI might give it a more descriptive name to avoid any ambiguity; I tend to use names like
timerHandler
.
There's no point in starting and stopping your timer in
init
.That reference to
().self
in thetarget
should just beself
.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.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
(thetarget
) 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() {
}
}
Related videos on Youtube
Juan Burolleau
Updated on June 04, 2022Comments
-
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 about 6 yearsYou run it on playgrounds? i get this error: error: Execution was interrupted, reason: EXC_BAD_ACCESS (code=1, address=0x0)
-
Umair_UAS over 4 yearsThanks for the detailed answer bro. It helps a lot.