Reputation: 223
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
Reputation: 8092
Let's clear this up.
First some prerequisites…
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 String
s 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.
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…
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
.
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,
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