mutantChickenHer0
mutantChickenHer0

Reputation: 223

Swift Optionals - Syntax logic

Looking at this example of conditionals I am confused. Here is the code and my interpretation

var animal = Animal(name: "Lenny", species: "lemur", tailLength: 12)
animal = Animal(name: "Gilbert", species: "Gorilla", tailLength: nil )

if let tailLength = animal.tail?.length {
    print("\(animal.name)'s tail is \(tailLength) long")
} else {
    print("\(animal.name) doesn't have a tail.")
}

We have a variable "Animal". Not all animals have tails and so some tailLength values will return nil.

To me, an optional is something with options - in this case it can be an Int or nil (In the case of Gilbert above).

Now, when we want to unwrap it, we are using chaining to check if it will return nil. If it returns an Int, we return it. If it returns nil, we move to the else statement.

Why is the syntax -

if let tailLength = animal.tail?.length

rather than

if let tailLength = animal.taillength?

or

if let tailLength = animal.tail.length?

Note: New to programming. I'm aware that perhaps the answer requires some prerequisite knowledge of other languages and common syntax.

Upvotes: 0

Views: 124

Answers (1)

Matthew Seaman
Matthew Seaman

Reputation: 8092

Let's clear this up.

First some prerequisites…

Optionals

Optionals do not necessarily imply options. Instead, "optional" simply implies that the value may or may not exist, hence it is "optional". Think of Optionals as containers sized just right so that they may only hold one specific type. For example, an Optional created specifically to hold an Int could never hold a String, and vice versa. By this definition, Optionals sound exactly the same as variables and constants, but the big difference to note is that unlike regular variables and constants, the containers for Optionals may contain "no value at all", AKA nil or they may contain a value of their specified type. There are no other options.

Optionals are used anytime it makes sense for there to be no value at all, since nil is a better representation of "nothing" than say, -1 for an Integer.

For example,

class Contact {

    var name: String
    var phoneNumber: String?
    var emailAddress: String?

    init(name: String, number: String?, emailAddress: String?) {
        self.name = name
        self.phoneNumber = number
        self.emailAddress = emailAddress
    }

}

This is a vastly simplified version of what you might use to represent an entry in a contacts app. For simplicity, we'll use Strings to represent each of these properties. The question mark after the type name means that we do not want these properties to be of type String, but instead String?, or "Optional String". The app requires the user to enter their name, but the user may or may not choose to provide a phone number or email address, so the corresponding properties are declared as optional Strings. If the user provides an email address, for example, then the emailAddress property will hold a String representing the address. If the user had not provided an address, the emailAddress property would not hold a String but instead nil, or the absence of a value.

Behind the scenes, an Optional is just an enumeration with two cases: None, and Some with a wrapped value of the type the optional was declared. If you're not familiar with Swift enumerations or this is confusing to you, please ignore this paragraph. It is not important in order to understand Optionals.

Optionals always have only two options. Either nil or a value of the type they were declared.

Unwrapping Optionals

Now suppose we want to access an Optional at some point. We can't just reference it because there is a possibility that it could be nil and our calculation wouldn't function properly. For example, if we wanted to access the user's email address, we might try something like

let user = Contact(name: "Matt", number: nil, emailAddress: nil)
sendEmail(user.emailAddress)

Assuming that the sendEmail function requires a String representing the address as an argument, Swift will report an error and not let the code compile as is. The problem is that we cannot guarantee that there will actually be a String to pass sendEmail. sendEmail requires a String but we are trying to pass it a String?. These are not the same type. If we know that the value will indeed exist, we can just append an exclamation mark immediately after the value that is optional. So, continuing with our example,

let user = Contact(name: "Matt", number: nil, emailAddress: nil)
sendEmail(user.emailAddress!)

Here, the exclamation mark tells Swift that we are sure there will be a value in the emailAddress property, so Swift goes ahead and converts the String? to a String with the value. If at runtime it turns out that emailAddress evaluates to nil, we will get a fatal error. Of course, in this case, we will get a fatal error at runtime because emailAddress did indeed contain nil.

Much of the time, however, and especially in our Contact example, we will not be able to guarantee that a emailAddress exists. Now introducing Optional Binding…

Optional Binding

For times when we simply do not know whether a value exists, but would like to use that value for something if it does, we can use Optional Binding.

Suppose we want to send the user an email if they provided their address. Well, we would need to ensure the value is not nil, assign it to a constant if it isn't, and do some workaround if it is. Here's a perfectly valid way of doing so:

if user.emailAddress != nil {
    let address = user.emailAddress!
    sendEmail(address)
} else {
    promptForEmailAddress()
}

Here, we are checking to make sure the address is not nil. If it is, we will ask the user to give it to us. If the address does exist (if it's not nil), we will assign it to a constant by force unwrapping it with an exclamation mark. We can do this because if the if statement resolves to true, we can be absolutely certain that user.emailAddress will not be nil. sendEmail is then satisfied because address is of type String and not String?, since we unwrapped it first.

But because this is such a common pattern in Swift, the language defines a simplified way of doing this (Optional Binding). The following code snippet is exactly equivalent to the snippet above.

if let address = user.emailAddress {
    sendEmail(address)
} else {
    promptForEmailAddress()
}

With optional binding, you combine the assignment operation with the if statement. If whatever is on the right of the equals operator (assignment operator) evaluates to nil, the else clause is called and the address constant is never created. If, however, the expression on the right of the equals operator does indeed contain a value, in this case a String, then the address constant is created and initialized with whatever String value was stored in emailAddress. The type of address is therefore String and not String? because if the constant is created, we know it is not nil.

Optional Chaining

Now, sometimes you need to access properties that are at the end of a long chain of objects. For example, suppose emailAddress was not a String but instead a special Email object that held several properties of its own, including a domainName declared as a 'String'.

visitWebAddress(user.emailAddress.domainName)

If one of these values (user, emailAddress, or domainName) was declared as an Optional, this would not be a safe call and Swift would yell at us because our imaginary visitWebAddress function expects a String, but got a String?. Actually, this is not even valid code because we should have a ? after emailAddress. What's going on here? Well, remember that emailAddress was declared as a String?, or "optional string". Anytime we want to access a property using dot syntax on an already optional value, we must use either an exclamation mark (which forces the Optional unwrapped and returns the value which must exist) or a question mark. Whether we choose ! or ?, the symbol must immediately follow the property that was declared to be Optional.

visitWebAddress(user.emailAddress?.domainName)

This statement is syntactically correct. The question mark tells Swift that we realize emailAddress could be nil. If it is, the value of the entire statement user.emailAddress?.domainName will be nil. Regardless of whether emailAddress actually is nil or not, the resulting type of user.emailAddress?.domainName will be String?, not String. Therefore, Swift will still yell at us because visitWebAddress expected a String, but got a String?.

Therefore, we could use a combination of Optional Binding and Optional Chaining to create a simple and elegant solution to this problem:

if let domain = user.emailAddress?.domainName {
    visitWebAddress(domain)
}

Remember, the expression on the right side of the equal operator is evaluated first. As already discussed, user.emailAddress?.domainName will evaluate to a String? that will be nil if the email address was not specified. If this is the case and the expression evaluates to nil, no constant is ever created and we are done with the if statement. Otherwise, the value is bound (hence "Optional Binding") to a created constant domain (which will have type String) and we can use it inside the if clause.

As a side note, Optional Chaining can sometimes result in no action at all taking place when a value is nil. For example,

textField?.text = "Hello"

Here, if textField evaluates to nil (for example if the UI has not loaded yet), the entire line is skipped and no action is taken.

Now for your exact question,

Code Analysis

Based on your posted code sample, this is what is likely going on. The Animal class probably looks something like this at a minimum:

class Animal {

    let name: String
    let species: String
    let tail: Tail?

    init(name: String, species: String, tailLength: Int?) {
        self.name = name
        self.species = species
        if let lengthOfTail = tailLength {
            self.tail = Tail(length: lengthOfTail)
        }
    }

}

Now, you're probably wondering what is up with all this mention of a Tail object, so this is probably also existent somewhere behind the scenes, looking like this at a minimum:

class Tail {

    let length: Int

    init(length: Int) {
        self.length = length
    }

}

(Now going back and looking at Animal should make more sense.)

Here, we have a Tail object with one property called length, of type Int (not optional). The Animal class, however, contains a property called 'tail' which is of type Tail?, or "optional Tail". This means that an Animal may or may not have a tail, but if it does it is guaranteed to have a length of type Int.

So here…

if let tailLength = animal.tail?.length {
    print("\(animal.name)'s tail is \(tailLength) long")
} else {
    print("\(animal.name) doesn't have a tail.")
}

…a ? immediately follows tail because it is the property that is optional. The statement animal.tail?.length has a type of Int? and if it is nil because tail is nil, the else clause is called. Otherwise, a constant tailLength is created and the tail length as an Int is bound to it.

Note that in the Animal initializer, it simply took an Int? for tailLength, but behind the scenes it checked to see if it was nil and only if it wasn't did it create a Tail object with the specified length. Otherwise, the tail property on the Animal was left nil, since nil is the default value of a non-initialized optional variable or constant.

Hope that clears up all of your questions on Optionals in Swift. The Swift Language Guide has some great content on this, so check it out if needed.

Upvotes: 2

Related Questions