http://lithium3141.com/blog/2014/06/19/learning-swift-optional-types/
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
// type is Int and must have a value. If it is not initialized, it won't compile. This eliminates an entire class of potential runtime errors that would arise from using an uninitailized value. //var y: Int // valid //print(y) // Call when not initailized would give us an error. "Variable 'y' used before being initialized" var x: Int = 45 print(x) //let a : String = nil // cannot initialize Non-Optional type to nil // must be an optional type to initialize to nil var a: String? = nil print(a) // prints nil a = "Ricky" print(a) // prints Optional("Ricky") |
“But wait,” you say, “Int is a value type, not an object! How can I use nil for a value?…”
Well, you’re right. NSInteger didn’t have a nil value (or, rather, using nil with the right coercion would get you an integer with a value of 0).
Instead, we defined a ton of marker values that meant “no value:”: 0, 1, NSIntegerMin, NSIntegerMax, and NSNotFound all mean “nothing” in some API.
When you stop to think about it, this is really a limitation: by not having a consistent, defined way of saying no integer value, we’re layering a tiny bit of additional complexity around any use of such a value, then attempting to paper over it with documentation. Want to find an object in an array? Well, if that object doesn’t exist, you get NSNotFound – but if you try to find a nonexistent row in a table view, you get -1 instead.
1 2 3 4 |
enum Optional<T> { case None case Some(T) } |
Swift defines a new type called Optional that always has exactly one of two values: a defined “nothing” value called None, or a wrapped-up value of some other type T.
It’s as if Swift can take regular values and place them inside a box, which may or may not be empty:
In this example, the first integer is a plain Int type.
The second and third, though, are both of type Optional
Notice that the third value here is actually an “empty box” (the None value), even though its type is Int?.
This ability, to pass around None anywhere an optional type can go, is how Swift can provide things like nil for value types like Int (or, for that matter, any type, whether value or reference). Since this value will have the same type as a “real” value wrapped up in Optional, they can both be represented in the same variable without trying to rely on special values to stand in for the concept of “no value.”
Given:
1 2 3 4 5 6 |
func double(x: Int) -> Int { return 2 * x } let y: Int? = 42 print(double(x: y)) // error: Value of optional type 'Int?' (y) not unwrapped |
We need some way of getting at the value inside an optional’s box – and, for that matter, checking whether such a value exists at all! Thankfully, Swift has us covered with the ! operator, which extracts the value out of an optional:
1 2 |
let y: Int? = 42 println(double(y!)) // prints '84' |
This works great for optionals that have a value. But what about those that don’t?
1 2 |
let y: Int? = nil // same as Optional.None println(double(y!)) // runtime error: Can't unwrap Optional.None |
The ! operator only applies to optionals that have an actual value inside them. If your optional has nil (an alias for .None), it can’t be unwrapped and will throw a runtime error.
Let’s make our code a bit smarter. Instead of unconditionally unwrapping our optional value, we can check whether the value is nil first – much like we might have done in Objective-C.
Not very good way of checking whether the value is nil
1 2 3 4 5 6 |
let y: String? = nil if y != nil { // optional cannot be used as a boolean. Test for != nil instead print ("y optional is valid") } else { print("y optional is invalid. Nothing there!") } |
Swift has us covered here too, with a syntax called optional binding. By combining an if and a let statement, we can write a concise one-line check for a newly-bound variable that is only conjured into existence if there’s a real value to go along with it:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
// essentially asking, does myInt contain a non-optional value of type Int, that is y if let y: Int = myInt { print("y = \(y)") } else { print("No value to double!") // prints "No value to double!" } // if using Optional /* // essentially asking, does myInt contain a optional value of type Int, this always results in true if let y: Int? = myInt { print("y = \(y)") } else { print("No value to double!") // prints "No value to double!" } */ |
Chaining – calling methods on those variables
Your program might have some custom classes – most do, after all – and you could want to call a method on an variable that might be an instance, or might be nil.
Developers can make use of optional chaining to call methods on potentially-nil objects:
1 2 |
let y: SomeClass? = nil let z = y?.someMethod() // will produce nil |
By sticking a ? between the variable name and method call, we can indicate that we want either a real answer back (in the event that y is a valid instance) or another nil (in the case that y is itself nil).
1 2 3 4 5 6 7 8 9 |
class SomeClass { func someMethod() -> Int { return 42 } } let y: SomeClass? = nil let z = y?.someMethod() // of type Optional<Int> with value nil, due to someMethod's function type "() -> Int" print("\(type(of: z))") |
Even though someMethod() is declared to return an Int, z gets type Optional
This might seem like a hassle, but can actually be helpful, especially when combined with optional binding from above. If we stick with the same class definition, we can try something like this:
1 2 3 4 5 6 |
let y: SomeClass? = nil if let z = y?.someMethod() { // z is a non-optional, meant to be used to extract value from an optional if there is valid data println(double(z)) } else { println("Couldn't get value from someMethod()") // will reach here if y is nil, or someMethod() returns nil } |
This remains concise while still dealing with all the various concerns we might have:
If y is nil (as it is here), the optional chaining will still allow us to write this code without a type error.
If y is nil or someMethod() returns nil, the optional binding will catch that case and avoid giving us a nil value for non-optional z.
In the event we do get a z, we’re not required to hand-unwrap it because it’s optionally bound.
All in all, this is a pretty clean system for passing around nil values for just about any type. We get some extra type safety out of the deal, avoid using specially defined values, and can still be just as concise as Objective-C – if not more!
Rough Edges
Unary ? operator – Not valid for Swift 3.
It’s valid Swift to take an optional variable and throw a ? at the end (Not for > Swift 3). However, unlike the unwrapping operator !, appending ? doesn’t actually affect the variable in any way: it is still optional.
Surrounding if checks will still look to see if the variable is nil, rather than evaluating its contained truth value (if any).
This can cause extra trouble when combined with Optional Bool
Since the Optional type is defined using generics (it can wrap any other type in the language) it’s possible to construct an optional boolean variable. In fact, it’s virtually mandatory the language allow this: to special-case Bool to disallow optionals would be an exceptional change, requiring serious modifications to the language or the Optional type.
That does, however, lead to a way developers can construct a kind of three-state variable: an Optional
1 2 3 4 5 6 7 8 9 |
let boolX: Optional<Bool> = false if boolX != nil { //evaluates NOT the value of the optional (boolX!), rather, it evalutes to see if boolX contains something //In other words, it DOES NOT evaluate the underlying truth value of boolX! print("true") } else { print("false") } // prints "true" |