iosswiftxcodeuikitswift-optionals

What is the best method in preventing this Optional-related crash?


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

}

Solution

  • var timer: Timer!
    

    Means that you're telling the compiler that you are sure that timer will never be nil, 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 to reset(_:) or stop(_:) before a start(_:).

    timer?.invalidate() //added ? to timer
    

    fix the crash because now you are accessing timer through optional chaining.

    But it does not solve the potential issue everywhere so declaring:

    var timer: Timer?
    

    is safer