- Optionals 101
- Unwrapping Optionals
- Optional Chaining
- Optional Mapping
- Unmanaged Wrappers
- Wrap-up
Optional Chaining
In Swift, you chain methods and properties by appending period-delimited selectors. Each function in the chain returns an intermediate value. This allows calls to be joined into a single statement without requiring variables that store intermediate results:
soundDictionary.description.characters.count
This approach creates a fluent interface, which is ideally a parsimonious and more readable expression of a set of operations you want to consider as a single unit. A danger, of course, lies in over-chaining. If you’re producing enormous lines of code that are difficult to debug and hard to read and that cannot be easily commented or differentiated on updates, you’re probably doing this wrong. Ask yourself, “Would I ever need to put a breakpoint in this statement or step through it?” If the answer is yes, you are over-chaining.
Swift introduces a powerful feature called optional chaining. Swift method calls may return optionals, and you must take this into account when forming chains. Swift provides a way that an entire chain can fail gracefully on encountering nil.
Optional chaining involves adding question marks after optional values. For example, you might look up an animal in the sound dictionary and use optional chaining to return a capitalized version of the sound:
soundDictionary[animal]?.capitalizedString // Moo or nil
Even though capitalizedString normally returns a non-optional, this chain returns String?. It may succeed or fail, depending on the lookup.
Add question marks to any chain participants that return optionals:
soundDictionary[animal]?.characters.first?.hashValue // returns Int?
You can add forced unwrapping to any chain item by replacing the question mark with an exclamation point. This usage comes with the same forced unwrapping dangers discussed earlier in this chapter:
soundDictionary[animal]!.capitalizedString // Moo or Runtime Error
Here’s a real-world example of where you might use optional chaining to simplify an if-let pattern. This code extends Array to return the index of a possible maximum element. Swift’s standard library maxElement() function returns an optional based on whether a sequence has values to compare (Apple writes in the standard library, “Returns the maximum element in `self` or `nil` if the sequence is empty.”):
extension Array where Element:Comparable { var maxIndex: Int? { if let e = self.enumerate().maxElement({$1.element > $0.element}) { return e.index } return nil } }
Introducing optional chaining greatly simplifies this code, enabling you to shortcut the index lookup and returning nil if the maxElement call fails. Recipe 3-2 returns the index of an array’s maximum value.
Recipe 3-2 Using Optional Chaining to Shortcut Evaluations
extension Array where Element:Comparable { var maxIndex: Int? { return self.enumerate().maxElement( {$1.element > $0.element})?.index } }
Extend Recipe 3-2’s functionality to all collection types with the following snippet:
extension CollectionType where Generator.Element: Comparable { var maxIndex: Index? { return self.indices.maxElement({self[$1] > self[$0]}) } }
Selector Testing and Optional Chaining
Optional chaining isn’t just about transforming your code into bite-sized lines. It also acts as shorthand to test whether an item responds to a method or property selector. Optional chaining offers a rough equivalent to Objective-C’s respondsToSelector: method, enabling you to determine whether it’s safe to execute calls on particular instances.
Commonly, you work with subclasses that are directly related to each other but that implement distinct method sets. For example, you might retrieve a collection of SpriteKit nodes from a scene and then adjust the line widths of the shape nodes. This snippet uses a failable cast followed by an optionally chained property assignment:
for node in nodes {(node as? SKShapeNode)?.lineWidth = 4.0}
This selector-testing approach also works in pure Swift, as in the following example:
// Root class put two subclasses class Root {func rootFunc() {}} class Sub1: Root {func sub1Func() {print("sub1")}} class Sub2: Root {func sub2Func() {print("sub2")}} // Create heterogeneous array of items var items: [Root] = [Sub1(), Sub2()] // Conditionally test and run selectors (items[0] as? Sub1)?.sub1Func() // runs (items[0] as? Sub2)?.sub2Func() // no-op, nil
This snippet constructs a heterogeneous array of Root subclasses. It then performs conditional casts and uses selector tests before calling class-specific methods.
Selector testing enables you to test whether a method exists before constructing a new instance. Adding the question mark ensures that the NSString call won’t fail with a runtime “unrecognized selector” exception:
let colorClass: AnyClass = UIColor.self let noncolorClass: AnyClass = NSString.self colorClass.blueColor?() // returns a blue color instance noncolorClass.blueColor?() // returns nil
This is a special behavior of AnyClass and AnyObject that works only with Objective-C methods, for compatibility with Class and id. These are special cases because these types return functions as implicitly unwrapped optionals. Other types don’t do that.
Subscripts
Contrary to expectations, optional chaining with subscripts doesn’t introduce safe lookups. This is an important factor that you should internalize as soon as possible and recognize in your code. In the following example, if you try to access index 8 (aka the ninth element of this six-element array), your code dies with a fatal Array index out of range error:
let array: Array? = [0, 1, 2, 3, 4, 5] array?[0] // 0 // array?[8] // still fails
In this example, the question mark does not qualify the lookup for safety. It is required for subscripting after array, which is optional. With subscripts, you add chain annotations in-line after the optional value and before the subscript brackets.
Optional chaining is meant solely to set and retrieve values for subscripts used with optional values. It does not and cannot short-circuit failed subscripts unless you build a failable subscripting extension, as in the following example:
extension Array { subscript (safe index: UInt) -> Element? { return Int(index) < count ? self[Int(index)] : nil } }
Once you add a simple array safe-index extension, you can optionally chain the safe version of the subscript. In the following calls, the Element? results of the safe: subscript are now optional and can be chained:
print(array?[safe: 0]?.dynamicType) // nil print(array?[safe: 8]?.dynamicType) // Optional(Swift.Int)