Context
I am beginner learning Swift and I’m trying to make sure I understand this bug "fix". I followed along a Youtube video showing how to create a timer in Xcode. I changed some things around in effort to learn some things but the issue that arose was from the original code.
The Bug
Every time I started the simulator, if I pressed the Stop or Reset button without first starting the timer, the app would crash and show this error on the same line as timer.invalidate()
:
Unexpectedly found nil while implicitly unwrapping an Optional value
My Fix
To fix the issue I added ?
to make it timer?.invalidate()
as seen in the code below. I tried this after some Googling to get a very loose understanding of what is going on.
Did this work because the timer did not exist yet and therefore it was "nil"?
Is this the best way to prevent the crash? It seems that I could also change the variable timer to use ?
instead and have it work, but only after adding ?
to the timer.invalidate() line in the step function as well as the others. Clearly I need to brush up on my understanding of Optionals.
import UIKit
class ViewController: UIViewController {
@IBOutlet weak var label: UILabel!
var timeRemaining: Int = 10
var timer: Timer!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
@IBAction func start(_ sender: Any) {
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(step), userInfo: nil, repeats: true)
}
@IBAction func stop(_ sender: Any) {
timer?.invalidate() //added ? to timer
}
@IBAction func reset(_ sender: Any) {
timer?.invalidate() //added ? to timer
timeRemaining = 10
label.text = "(timeRemaining)"
}
@objc func step() {
if timeRemaining > 0 {
timeRemaining -= 1
} else {
timer.invalidate()
}
label.text = "(timeRemaining)"
}
}
2
Answers
Don’t declare your timer as an implicitly unwrapped optional.
Change
to
(Replace the exclamation point with a question mark.)
Then yes, change your code to read
timer?.invalidate()
Edit:
Note that your button code should invalidate the timer as well:
If you don’t invalidate your timer before creating a new one, you could have multiple instances of your timer running at the same time, and when you replace the one in the variable
timer
with a new one you don’t have access to the previous one any more to stop it.Means that you’re telling the compiler that you are sure that
timer
will never benil
, so if you try to use the variable and it is nil you got a crash. This is probably what happened to you: a call toreset(_:)
orstop(_:)
before astart(_:)
.fix the crash because now you are accessing
timer
through optional chaining.But it does not solve the potential issue everywhere so declaring:
is safer