Optional Types
Optional types provide a way to indicate that a value may be nil as opposed to having a value of some given type. An optional is represented in swift via a simple template enum:
enum Optional<T> : NilLiteralConvertible {
case None
case Some(T)
...
}
So you can declare and use an optional variable like any other enum:
var temperature : Optional<Float> = .None
if temperature == .None {
temperature.Some = Optional<Float>(70.6)
}
Given how prevalent and useful optional values are the Swift compiler has built in support for special handling of optionals through the ? and ! operators (syntactic sugar). In addition, swift features such as literal convertibles enable the Optional type to convert nil to/from Optional.None. Thus we can write the above as follows:
var temperature : Float? = nil
if temperature == nil {
temperature = 70.6
}
The swift ? operator is used to specify that the value is an Optional and the compiler will automatically represent or wrap that value in an optional.
The ? operator
The example above is using the ? operator. The ? operator means the value/type is an Optional. It’s really just some syntactic sugar to make code more readable. When used on a variable type declaration, this tells swift to treat/convert the type to an Optional type. Thus the following are 100% equivalent:
var temperature : Float? = .None
var temperature : Optional<Float> = .None
In addition, because Optional types support NilLiteralConvertible the .None can be replaced with nil as well:
var temperature : Float? = nil
var temperature : Optional<Float> = nil
Now let’s get to how we set and access Optional values.
Wrapping and Unwrapping
Keeping with our same example, you can see where the value 70.6 is automatically wrapped (converted to an optional):
temperature = 70.6 // Auto wrapped
temperature = Optional<Float>(70.6) // Explicitly wrapped
The swift compiler knows when we are assigning into a Optional and will automatically wrap the value into an Optional. Note: This auto wrapping doesn’t work for other types. It is special to the Optional type.
The question is how do we get the value back out of an optional? If you try to just access the value, you will get a compiler error as the types don't match:
let currentTemperature : Float = temperature // ERROR, TYPE MISMATCH
We could do it like one does it for any enum by using a switch statement:
let currentTemperature : Float
switch( temperature )
{
case .None: throw someError
case .Some(let value): currentTemperature=value
}
However, this is a rather long winded way of getting access to the value and would destroy code readability if this had to be done every time an Optional’s value was needed. So Swift has several ways to unwrap an optional value:
- The ! operator can be used to specify the value is an optional and thus may be nil, but the optional will be implicitly unwrapped automatically upon access. With ! if you try to access the value and it’s nil you will get an exception/crash.
- The optional binding method uses an if let statement to safely unwrap an optional
- The guard statement is another form of optional binding that safely unwraps an optional
- The optional chaining method allows safe chaining of operations that return optionals
Implicitly Unwrapping (!)
The ! operator can be used to specify the value is an optional and thus may be nil, but the optional will be implicitly unwrapped automatically upon access. With ! if you try to access the value and it’s nil you will get an exception/crash. Because of this, you need to use ! carefully.
Keeping with our same example, you can force temperature to unwrap its operational by using the ! operator. However, an exception will be thrown if the value is nil:
let currentTemperature : Float = temperature! // Exception thrown if nil
You can make this safer by checking against nil before you try and access the value:
if temperature != nil {
let currentTemperature : Float = temperature!
print( “\(currentTemperature )” )
}
You can also use a ! operator in the declaration of a variable such as:
var temperature2 : Float! = 72.3
When used in the variable declaration or function parameter, the variable will be treated as an optional just let the ? operator. However, it will be automatically unwrapped on access. So the following will work:
let currentTemperature : Float = temperature2 // temperature2 auto unwrapped
However, if temperature2 is nil an exception will be thrown so use this carefully. Swift also has several ways to unwrap optionals safely.
Optional Binding (if let)
To help with unwrapping of optionals in a safe way, swift has a special if let operation that only succeeds when an optional isn’t nil. So building on our example above, we can use if let to safely unwrap temperature to currentTemperature:
if let currentTemperature = temperature {
// Only get here if temperature != nil
// currentTemperature will have a value
// currentTemperature’s scope is only this block
print( “\(currentTemperature )” )
}
Extra conditions that have to be met can be added via the where operator:
if let currentTemperature = temperature where currentTemperature < 32.0 {
// Only get here if temperature != nil
// currentTemperature will have a value less than 32.0
// currentTemperature’s scope is only this block
print( “It’s freezing \(currentTemperature )” )
}
One of the issues with optional binding is you can end up with a fair amount of nesting (pyramid of doom). Swift 1.2 helped this out a little bit by allowing you to specify multiple optionals:
if let currentTemperature = temperature,
let value2 = optional2 where currentTemperature < 32.0 {
// Only get here if temperature != nil and optional2 != nil
// currentTemperature will have a value less than 32.0
// currentTemperature’s scope is only this block
print( “It’s freezing \(currentTemperature )” )
}
However, since the unwrapped values (currentTemperature, value2), are only accessible within the scope of the if let block it can still force unwanted nesting. Swift 2.0 introduced guards to help with this problem.
Guards
Swift 2.0 introduced the concept of guard statements. Guards can be looked at in some ways as the opposite of if let optional binding. Guard statements execute only if the optional’s value is nil as opposed to if let executing only if the optional isn’t nil. Guards also allow the assigned unwrapped non-nil value to be used after the guard statement.
guard let currentTemperature = temperature else {
print( “Unknown temperature” )
return // Must exit calling scope of guard
}
// currentTemperature exists and is non-nil after the guard!
print( “\(currentTemperature )” )
The code executed by the guard when the optional is nil must exit the calling context of the guard statement. If the guard statement is in a loop then continue or break may be used. If the guard is in a function then return is typically used. Basically, execution within the context of the block of code containing the guard isn’t allowed to continue beyond the guard statement when the guarded optional is nil.
Optional Chaining
Optional chaining allows you to place a ? operator after the optional value on which you wish to call a property, method, etc. This will force an optional result to be returned, and if the unwrapped optional is nil then the chain stops and nil is returned. For example:
let optionalValue = anOptional?.aProperty // somevalue would be an optional
if let value = optionalValue {
print( “aProperty = \(value)” )
}
As this is called optional chaining, these can be chained as needed. For example:
If optional, optionalProp1, or optionalProp2 is nil then nil will be returned as the optionalValue.
Of course these can also be intermixed with the ! operator when you want to force an unwrap. However, if the implicit unwrapped value is nil an exception/crash will happen. For example:
let optionalValue = optional!.optionalProp1?.nonOptionalProp.optionalProp2!
In the above example if optional is nil then as exception/crash will happen. If optionalProp1 is nil then nil will be returned and optionalProp2 won’t get evaluated. If optionalProp2 isn’t nil but optionalProp2 is nil then it will exception/crash.
If the ? operator isn’t used in the chain then the actual value will be returned as opposed to an optional value. For example.
let value = optional!.optionalProp1!.nonOptionalProp.optionalProp2!
In the above example value will be the value of optionalProp2, unless one of the optionals is nil in which case you will get an exception/crash so this is very much unsafe and would be of bad practice!
Quick Summary: Questions (?) and Bang (!)
When you see the ? operator it means the value is an optional and thus may be nil. When you see ! it also means the value is an optional and thus may be nil, but the optional will be implicitly unwrapped automatically upon access. With ! if you try to access the value and it’s nil you will get an exception/crash.
Conclusion
I hope you found this helpful and interesting. If you see anything incorrect or if you have different thoughts please share.
Thanks,
Tod Cunningham
References:
- http://nshipster.com/swift-1.2/
http://nshipster.com/swift-literal-convertible/ - http://www.codingexplorer.com/the-guard-statement-in-swift-2/
- http://blog.scottlogic.com/2014/12/08/swift-optional-pyramids-of-doom.html
- http://www.touch-code-magazine.com/swift-optionals-use-let/
- http://lithium3141.com/blog/2014/06/19/learning-swift-optional-types/
- https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/TheBasics.html#//apple_ref/doc/uid/TP40014097-CH5-ID309
No comments:
Post a Comment