Learning Swift - Optionals
Today I plan to discuss optionals since they were a feature of Swift that I found difficult to grasp at first.
What is an optional in Swift?
An optional is a variable of a specified type that can also be nil.
Why does this matter?
In Objective-C, any object type could be nil. If you declared a variable like this:
NSString *myString;
then myString
was set to nil by default.
But this could cause issues, especially as Objective-C does not complain if you send a message to nil. This could lead to bugs that were very difficult to track down.
The other big use for nil is when returning from a function which has found no appropriate data to return. The classic example is when looking for the index of an element in an array. What should be returned if the element is not found in the array?
Some languages return -1, Objective-C uses NSNotFound
, but you have to know
what each language is going to do. The more logical answer is nil. However if
your function is expected to return an integer, then it cannot return nil
because nil is not an integer.
This is where optionals come in: if the function is expected to return an optional integer, it can return with an integer with the index of the matching element, or it can return nil if the element was not found. This is much clearer and less prone to error.
How does Swift handle optionals?
One of the first things that struck me about Swift was how clean the code looked, without so many non-alphanumeric characters scattered around. Gone were all the:
* ; [ ]
But instead, Swift code sprouted:
! ?
What were these?
The key to understanding optionals is to realise that when you declare an optional variable of a certain type, you are actually declaring a box that can hold a variable of that type or can hold nil.
Once you grasp that concept, it all becomes much more obvious.
var optionalInteger: Int?
The ? indicates that this is an optional variable. It does not have to be initialised as it is already set to nil which is valid for an optional variable. Without the ? this would require initialisation as it would not be valid for it to be nil.
Setting an optional
Setting the value of an optional variable is just the same as any other variable:
optionalInteger = 3
optionalInteger = 42
Getting an optional
The difference arises when you need to get the data out of the optional variable in order to use it. This process is called un-wrapping and it means to get the variable value out of the ‘box’ it is stored it.
The most obvious way is to use !
let newInteger = optionalInteger!
DO NOT DO THIS!
This is called forced-unwrapping and assumes that the optional variable is not nil. If the optional is nil, this will crash. In Xcode, when you connect interface elements from your storyboard to a Swift file, Xcode will use ! like this:
@IBOutlet weak var startButton: UIButton!
I have to assume Xcode knows what it is doing and the button will be available when needed, but you should not use ! - it is un-safe. By using it, you are vowing to the compiler that when it gets to that point, the optional value will not be nil. There are much better and safer ways of doing that.
Use ‘if let’
func doubleNumber(_ optionalInteger: Int?) -> Int? {
if let integerValue = optionalInteger {
// integerValue is not an optional
// and is guaranteed to contain an Int
return integerValue * 2
}
// no integer found in the optional,
// so return nil to indicate failure
return nil
}
Use guard
func doubleNumber(_ optionalInteger: Int?) -> Int? {
guard let integerValue = optionalInteger else {
// get out quickly,
// returning nil to indicate failure
return nil
}
// integerValue is not an optional
// and is guaranteed to contain an Int
return integerValue * 2
}
These two alternatives (if let
& guard
) do the same job but in opposite
ways. In both cases, they perform a conditional un-wrapping that may or may not
give a valid result. if let
checks if it is OK to proceed. guard
checks to
see if it is NOT OK to proceed. Which you use is really a matter of personal
preference and working out what is more logical in each case.
The guard
statement is really good for checking data early in a process and
making a quick exit it something is wrong. The if let
construct encloses your
success code inside a block and can sometimes leave the failure code a long way
from the check which can make it less obvious. The other potential issue with
if let
is the “pyramid of doom” common in early Swift code as demonstrated in
this rather contrived example:
func isValidAddressBookEntry(firstName: String?,
lastName: String?,
emailAddress: String?,
phoneNumber: String?) -> Bool {
if let validFirstName = firstName {
if let validLastName = lastName {
if let validEmail = emailAddress {
if let validPhone = phoneNumber {
return true
}
}
}
}
return false
}
Thankfully, Swift now allows us to chain both if let
and guard
statements.
Here is the previous example re-factored to use if let
:
func isValidAddressBookEntrySwift2(firstName: String?,
lastName: String?,
emailAddress: String?,
phoneNumber: String?) -> Bool {
if let validFirstName = firstName,
let validLastName = lastName,
let validEmail = emailAddress,
let validPhone = phoneNumber {
return true
}
return false
}
And here is the same function but using guard
which allows the inputs to be checked immediately and the function exited if the u=inputs are not valid. For a short function like this, the change is not really significant, but if the function does a lot of processing of the input data, checking first and getting out as soon as possible is more efficient.
func isValidAddressBookEntryUsingGuard(firstName: String?,
lastName: String?,
emailAddress: String?,
phoneNumber: String?) -> Bool {
guard
let validFirstName = firstName,
let validLastName = lastName,
let validEmail = emailAddress,
let validPhone = phoneNumber else {
return false
}
return true
}
Use optional chaining
The final way to deal with optionals safely is to use optional chaining:
struct SocialMediaAccounts {
var facebook: Person?
var twitter: Person?
}
struct Person {
var firstName: String?
var lastName: String?
var handle: String?
}
var socialMedia: SocialMediaAccounts?
socialMedia = SocialMediaAccounts()
var twitterAccount = Person()
socialMedia?.twitter = twitterAccount
let twitterHandle = socialMedia?.twitter?.handle
In this example, we have defined a SocialMediaAccounts
struct that holds
optional Person
structs for the various social media outlets. The
socialMedia
variable is defined as an optional and then created. A
twitterAccount
variable of type Person
is also created but contains no data
at the moment.
When assigning the twitterAccount
to the socialMedia.twitter
property, a ?
is inserted which checks to see that socialMedia
is not nil. If it is nil,
then execution of that line stops at the ? and nothing bad will happen.
In the same way, when trying to read back the twitter handle, we chained
together 2 optionals with ?’s. If either socialMedia
or socialMedia.twitter
is nil, that line will not complete. Again this is perfectly safe and the app
will not crash.
All the examples in this article are available in a Swift playground which has been updated to Swift 4 syntax.