Why are IBOutlet properties declared with exclamation points?

advertisement
Why are IBOutlet properties declared with exclamation points?
Notice that the IBOutlet properties in view controller classes are declared with exclamation points: @IBOutlet weak var recordButton: UIButton! @IBOutlet weak var stopButton: UIButton! @IBOutlet weak var recordingLabel: UILabel! Variables like these, declared with exclamation points, are called Implicitly Unwrapped Optionals. That is the kind of phrase that creates more questions than it answers. We will address two in particular: ● What are optionals? ● What does it mean to unwrap them? Optionals are one of the most unique aspects of the Swift language. They are the way that Swift handles nil values. Nil values inside object references can be dangerous. If a reference holds a nil value and you try to invoke a method on this value then you are stuck with two bad options: a runtime error, or a silent failure. Both dangerous. Swift takes the radically cautious position of banning the nil value, from primitives and object references alike. No variable declared directly with a data type can store a nil value. Wait, do we ever need nil values? Why have they been part of programming for so long? There are plenty of situations where it can be important for a variable to have a nil value. For example, in UIKit, there is a view class named UIImageView that is designed to display images. Oftentimes, UIImageViews are present in the view hierarchy as placeholders while they do not yet have images. These image views have an image property, but it should be nil when there is no image to display. For a simpler example, consider the toInt() method in the String class that converts strings to ints. let numString = “22” let badString = “this can’t be turned into an int” // Store 22 let x = numString.toInt() // Store nil let y = badString.toInt() In the first assignment statement x will get the integer value 22. The string “22” can be converted into an integer. But in the second assignment there is no reasonable value for y. Storing a nil value into y would be a reasonable way to represent “no value” in this case. What is Swift’s solution, if variables cannot store nil values? In order to accommodate nil values Swift has special optional types. Optional types store one of two things: either a value from the data type or nil. To create a variable using an optional data type you use the question mark after the data type. In the UIImageView the image property is declared as an optional with a question mark. The variable is not a plain old UIImage reference, it is a UIImage optional, to accommodate this possible nil value. var image: UIImage? In the code below the variable x is an int. The variable y is an optional type, based on the Int type. The variable x cannot store a nil value, but y can. Because y is not an Int, but rather an Int optional it can store either an int value, or nil. // Not OK var x: Int x = nil // OK var y: Int? y = nil What does it mean to unwrap an optional? A variable that is declared with an optional type has a little bit of mystery to it. At any given time it may store a value, or it may store nil. It is like a wrapped package that we cannot see inside. Because of this two­fold nature we cannot use optionals in the same way we use plain old variables. We need to unwrap them first. In the code below we have an Int optional named o and a plain old int named x. The compiler will not let us assign the optional value in o into x directly. var o: Int? var x: Int o = someString.toInt() x = o // Not legal There is a data type mismatch because o is still wrapped. It may have an Int value inside, or it may not. We can correct this by unwrapping o with an exclamation point. x = o! // Legal, but not safe The expression o! is not a conditional expression. It is unwrapped, so it will either be an int value, or it will be nil. We are no longer trying to assign an optional into a plain old int. We are unwrapping it first and storing the value into the int. It is legal, but it is a little dangerous. We cannot know if there will be a value inside, or a nil. And if o! is nil then we will get a runtime error when we try to store that nil into x. Only conditionals can store nil, and x is not a conditional. Swift allows us to safely unwrap the optional in an if statement. Inside the if statement the optional o is implicitly unwrapped. If it turns out that o stores a nil then the if statement does not execute, and there is no runtime error. This is the preferred Swift technique in situations where we cannot know whether or not an optional might have a nil. Let’s say we do not know the value of someString. We will want to proceed with caution: var o: Int? o = someString.toInt() if var x = o { // Do something with x. Safely. } That is a great technique. But it is fairly verbose, and a hassle when we have reasons to be confident that an optional is not nil. Is there a special kind of Optional that is automatically unwrapped in expressions? There is! Sometimes we know for certain that an optional variable is going to hold a value. The Outlets that we set up in Storyboard are an example. In these cases we can use a special kind of optional: an Implicitly Unwrapped Optional. These optionals are declared with exclamation points instead of question marks, and they store optional values, so they can store nils. But when we use them in expressions they are automatically unwrapped. In the code below o is declared as an Implicitly Unwrapped Optional using an exclamation point. var someString = “22” var o: Int! var x: Int o = someString.toInt() x = o // Legal! The variable o is still an optional, but we do not need to unwrap it when we use it. It is automatically unwrapped. So, why are IBOutlets declared as Implicitly Unwrapped Optionals? The UIButton and UILabel properties in our ViewController class fit the profile for variables that should be declared as Implicitly Unwrapped Optionals. Here are the two key criteria that tip us off: ● They need to be nil when the object is created. ● We can guarantee that they are not nil when we use them. They need to be nil when the ViewController object is created because the Storyboard mechanism does not set their values when init is invoked. But they will be set by the time viewDidLoad is invoked. By using Implicitly Unwrapped Optionals we get the optionals we need, with the convenience of using them confidently, without needing to unwrap them. 
Download