Swift! Optionals? A short guide. (And one trick)

Fri, 04/14/2017 - 11:28

If you are coming from Objective-C or you are new to swift. Optionals can be tricky to understand, but once you do they wont get in your way anymore.

Optionals are defined in properties of a Struct or Class, or method parameters. They are defined by following Type definition (Int, or String, or [String]) with a question mark, "?". 

They are another way of saying, sometimes this will have a value, and other times it wont. This will come in handy if you want to show information when it is set, but when it is not, do something else.

Consider the following Struct.

struct Person {
    var firstName: String = ""
    var lastName: String = ""
    var children: Int?

    init(firstName: String, lastName: String, children: Int?) {
        self.firstName = firstName
        self.lastName = lastName
        self.children = children
    }
}

 

We define a Struct named Person and it has three properties. firstName, lastName, and children. It also has one method, init. children is set as an Optional property of Person.

Now of note here in the initializer,

init(firstName: String, lastName: String, children: Int?)

The "children= Int?" portion. That does not mean that children is an optional parameter, you still have to pass children when initializing this. Its important to understand that this initializer definition requires children to be passed BUT it is of an option value. Meaning it can be nil, but you still have to pass something in. Lets assume that you pass nil into that initializer.

let kyle = Person(firstName: "Kyle", lastName: "Browning", children = nil)
print(kyle) // Person(firstName: "Kyle", lastName: "Browning", children: nil)

So whats the problem? Well, if you try to access children, XCode will tell you to force unwrap, using the "!" exclamation point. And this leads to a crash. Why?

print(kyle.children!) // Application crashes.

Force unwrapping is saying "I the developer KNOW there is a value in there". Two things, if thats the case, you should rethink why its optional. And two if you know it should be optional, well theres a safer way to get the value out IF it exists.

If Let and Guard

If let  will allow us to assign a value to something if it does exist and then chain in a closure that executes if the value is set. (Blog on that soon)

if let children = kyle.children {
    print(children)
}

This will never be called in our example because I have not set children. This also means our application wont crash, and XCode wont tell us to force unwrap.

You can also use a guard statement. Consider the following function to print children. 

func printFamilyWords(words: String?) {
    print(words!)
}

Using a guard statement, we can actually say, do something if it doesnt exist, and return. Otherwise, move on as planned.

func printFamilyWords(words: String?) {
    guard let familyWords = words else {
        print ("No family words set")
        return
    }
    print(words)
}

If you're the type that likes shorter code and you only want to display something simple, you can do so using double ??.

func printFamilyWords(words: String?) {
    print(words ?? "No family words set")
}

Okay, Strings and Ints are easy, what about an Array of Strings? I'm glad you asked.

For starters lets change our struct because honestly, If we had children, we would want their names, not just the count.

struct Person {
    var firstName: String = ""
    var lastName: String = ""
    var children: [String]?

    init(firstName: String, lastName: String, children: [String]?) {
        self.firstName = firstName
        self.lastName = lastName
        self.children = children
    }

    func printFamilyWords(words: String?) {
        print(words ?? "No family words set")
    }
}

So now children is an Array of Strings. Lets use what we now know to write a function without worrying about using that damn optional operator the "?".

func getChildren() -> [Person] {
    guard let children = self.children else {
        return []
    }
    var childrenArePeopleToo: [Person] = []
    for childName in children {
        childrenArePeopleToo.append(Person(firstName: childName, lastName: self.lastName, children: nil))
    }
    return childrenArePeopleToo
}

This function takes no parameters, but returns an Array of Persons. We double check that it is safe to use self.children. If its not, we return an empty array. If it is safe, we create a new array, loop over our children array, and create new Persons. Since lastName is not optional, we dont need to do anything special, and since self.children is Optional, we should guard it. This essentially says, if self.children is set, assign its value to children

let kyle = Person(firstName: "Kyle", lastName: "Browning", children: nil)
print(kyle.getChildren()) // []

And if we actually assign some children.

let children = ["Ava", "Aiden", "Jackson"]
let kyle = Person(firstName: "Kyle", lastName: "Browning", children: children)
print(kyle.getChildren()) // [Person(firstName: "Ava", lastName: "Browning", children: nil), Person(firstName: "Aiden", lastName: "Browning", children: nil), Person(firstName: "Jackson", lastName: "Browning", children: nil)]

At the core, an Optional type is actually an Enum. It has two cases, .none and .some(value) so there is one more way, if it suits you, to access the optional values. I have also taken the liberty of simplifying this even and used .map on the children Array. The code snippet below, does the exact same thing as func getChildren above.

func getChildrenSwitch() -> [Person] {
  switch self.children {
    case .none:
        return []
    case .some(let children):
        return children.map {Person(firstName: $0, lastName: self.lastName, children: nil) }
  }
}

I personally like the below codesnippet the best.

func getChildrenSwitch() -> [Person] {
    if let children = self.children {
        return children.map {Person(firstName: $0, lastName: self.lastName, children: nil)}
    }
    return []
}

If you have any questions, comment below and Ill reply as soon as I can!

comments

Michael (not verified)

Sun, 04/16/2017 - 13:29

I think that the guard is clearer. Also, prefixing the members with self is redundant.

public func getChildren() -> [Person] {
    guard let children = children else { return [] }
    return children.map { Person(firstName: $0, lastName: lastName, children: nil) }
}

kylebrowning

Mon, 04/17/2017 - 18:47

In reply to by Michael (not verified)

That does look a lot cleaner, not sure how I didn't think of it. Thank you!

Add new comment