Intro
Optionals are needed because swift disallows nil
values in all other types aside from Optionals. This characteristic of not allowing nil
values is not present in almost any other language.
Optionals are special in that they really only have two different values none
and some
, or a set and unset state. But when they are in the set state there can have an associated value. Kind of like a value on the side.
So Optionals can contain nil
, or no value in their unset state, or a value of some arbitrary type, declared by type casting, in their set state.
An Optional under the hood is just an enum. In other words...
// the <T> is a generic like as in Array<T>
enum Optional<T> {
case none
case some(T)
}
The following will both produce errors because swift will not allow nil to be stored in primitive types or object references.
var x: Int
x = nil
var c: UIColor
c = UIColor.redColor()
c = nil
In order to assign the value nil we need to use Optionals.
let x: String? = nil
let y: String? = "hello"
The above is just syntactic sugar for the following.
let x = Optional<String>.none
let y = Optional<String>.some("hello")
Conditions Where We Need A Value To Be nil
There are some cases where it wouldn’t make sense for a method to return a value.
y = Int(“abc”)
In this case the initializer is not able to convert the string to an int but it does not make sense to throw a fatal error, instead we need the option for the
Int()
method to return anInt
value or no value
Properties For Instances
Often instances have properties that can’t be initialized until after the instance is constructed
class MyViewController: UIViewController {
var something: UIButton
}
To avoid the issue above we add ?
to indicate the property is an Optional.
var z: Int?
class MyViewController: UIViewController {
var something: UIButton?
}
Conditionally Unwrapping Optionals
We can’t use an Optional's associated value without unwrapping them, and can’t determine the value until they are unwrapped.
Some thought must go into unwrapping Optionals especially if we do not know the expected value. One way to unwrap an Optional conditionally is to make use of if else
statements with variable assignment.
var z = Int?
z = assignIntVal(z)
if let intValue = z {
intValue * 2
} else {
print(“no value”)
}
Another Look At Conditionally Unwrapping Optionals
All we are doing when we conditionally unwrap an Optional is making use of a switch statement.
The above under the hood roughly translates to.
switch z {
case .some(let intValue):
// multiply intValue by 2
case .none:
// print "no value"
break
}
Implicitly Unwrapping Optionals
Implicit unwrapping requires certainty in the variable value as unwrapping a nil
value will have unintended side effects in the result of a fatal error.
Implicit unwrapping is also used to communicate to your code reader that you confident that the value will not be nil
and if it is you would like the app to crash
There are two ways to use implicit unwrapping. In declaring the variable and in unwrapping it.
Declaring
var x: Int!
This is saying when the variable is used in an expression it will automatically unwrap.
Unwrapping
print(x!)
This is saying no matter what automatically unwrap because we know the value is a type that can be printed.
Another Look At Implicitly Unwrapping Optionals
As with conditional unwrapping, all we are doing when we implicitly unwrap an Optional with !
is making use of a switch.
Given the following.
let x: String? = "hello"
let y = x!
Under the hood what's going on looks like this.
switch x {
case some(let value): y = value
case none: // raise an exception
}
Optional Chaining
Instead of using the if else
with variable assignment for properties where we would have to use multiple if
statements for we can chain the unwrapping to get at the end value.
So we can translate this.
if let temp1 = display {
if let temp2 = temp1.text {
let x = temp2.hashValue
...
}
}
To become the following. It's important to not the use of if
. Without the prefix of the if
statement we will get an Optional instead of an Int
.
// x is an Int
if let x = display?.text?.hashValue { ... }
// without the if x is an Int?
let x = display?.text?.hashValue { ... }
Chaining is powerful because when we use the ?
to try and unwrap and optional and the value turns out to be nil
swift will ignore the rest of the chain, so we could potentially have many var?.something?.else
and at any point if we unwrap a nil
swift will ignore the statement.
if let tabBar = viewController.tabBarController?.tabBar {
print("Here's the tab bar.")
} else {
print("No tab bar controller found.")
}
Optional Defaulting
One way to set a default value would be to use if else
statements like the following.
let s: String? = ... // might be nil
if s != nil {
display.text = s
} else {
display.text = " "
}
A cleaner way to do the same thing is to use the ??
operator.
display.text = s ?? " "
if s is
nil
use the default
Casting with !’s and ?’s
Optionals are required for down casting. Downcasting is when an object is a member of a particular class but can also act as a member of one of its subclasses. In down casting we cast the object explicitly as an instance of its subclass.
In the example below as we iterate through the drinkChoice
array we don’t know if the current item will be a cold or hot drink so we first try to unwrap as a ColdDrink
and if that fails we try to unwrap as a HotDrink
. If we were sure the array was made up entirely of one class we could use “as!” instead of the as?
like were using below.
class Beverage {
var category:String
init (category: String) {
self.category = category
}
}
class HotDrink: Beverage {
var pairing:String
init (category: String, pairing: String) {
self.pairing = pairing
super.init(category: category)
}
}
class ColdDrink: Beverage {
var vessel:String
init (category: String, vessel: String) {
self.vessel = vessel
super.init(category: category)
}
}
var drinkChoices = [
HotDrink(category: "coffee", pairing: "biscotti"),
HotDrink(category: "tea", pairing: "crumpets"),
ColdDrink(category: "lemonade", vessel: "glass"),
ColdDrink(category: "beer", vessel: "frosty mug")
]
// Generic drink offer for beverage in drinkChoices
for beverage in drinkChoices {
print ("Can I get you a \(beverage.category)")
}
// Type cast operators: __as?__ and __as!__
// Specific drink offer
for beverage in drinkChoices {
if let coldDrink = beverage as? ColdDrink {
print ("Can I offer you a \(coldDrink.vessel) of \(coldDrink.category)?")
} else if let hotDrink = beverage as? HotDrink {
print ("Can I get you some \(hotDrink.category) with \(hotDrink.pairing)?")
}
}
// Downcasting with as!
var coffeeArray: [Beverage] = [
HotDrink(category: "coffee", pairing: "biscotti"),
HotDrink(category: "coffee", pairing: "scones"),
HotDrink(category: "coffee", pairing: "biscotti"),
]
for beverage in coffeeArray {
let hotDrink = beverage as! HotDrink
print ("Can I get you some \(hotDrink.category) with \(hotDrink.pairing)?")
}