Swift Overview - Optionals

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 an Int 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)?")
}