This blog post looks at a few techniques that can be used to remove the deeply nested if-let statements that are a common sight in Swift code.
Introduction
I know Swift’s optionals are good for me, their strictness make my applications safer. But like that strange banana flavour medicine I recall the doctor giving me as a child, optionals leave a bad taste in my mouth!
In my previous blog post, I looked at how lazy properties can be used to avoid some of the initialisation woes cause by optionals. In this post, I’m turning my attention to optional pyramids!
What is an Optional Pyramid of Doom I hear you say?
It’s a name I borrowed from the JavaScript world. Web developers often find themselves having to deal with chains of asynchronous callbacks. These can lead to deeply nested code, termed a Pyramid of Doom.
With Swift, pyramids become an issue when you have to unwrap multiple optional values before performing some logic:
In the above code, the println
statement will only be executed if all three of the optional variable a
, b
, and c
are non nil. The more optionals your code relies on, the deeper the nesting becomes.
This becomes such an eye-sore that I’ve seen some developers suggest nil-checking then forced unwrapping to flatten the code. Not something I’d recommend.
Time to explore an alternative:
A Functional Alternative
It is actually quite a straightforward task to move the nested if-let statements into a utility function, where a given function is only invoked if all the optional parameters are non nil:
The above function unwraps the optional parameters and invokes the given function with the unwrapped results. Notice that the unwrapped variables have the same name, and shadow, the optional parameters, a naming convention proposed by Sam Davies, which I quite like.
The above function only takes two optional parameters, however the example above uses three. Unfortunately variadic parameters don’t work in this context, so you have to overload the if_let
function in order to vary the number of optional parameters:
Using the above tears down the pyramid, giving the following code:
Much better!
(If you want to test the if_let
function, have a go via this online Swift Stub)
Further Extensions
The if_let
function is quite a practical alternative to the nested if-let statements. Although it can be taken further, the next few sections explore some extensions that might be of interested
if-let-else
Whilst optionals force you to consider nil
and help avoid application crashes, you still need to do something sensible when nil checks fail.
The code snippet at the start of this post lacks and ‘else’ logic, time for a quick update:
Although, that’s not quite right is it - the ‘else’ logic is only executed if the first ‘if’ fails.
What you actually need is something more like this:
Yuck, I think I just spat out that medicine!
The if_let
function can be extended to add an else argument as follows:
Yes, I know, it uses a variable to avoid the need to check each if-let statements, but variables are just fine if you use them to create higher-order functions ;-)
Putting this into action, gives the following:
The syntax isn’t quite as neat as the previous, which made good use of trailing closure syntax, but it is still better that the more manual approach. Again, there’s a Swift Stub to play with.
if-let and cast
One area where Swift developers often encounter Pyramids of Doom is the parsing of JSON. In the early days of Swift there were quite a few people lamenting the code that this resulted in, although more recently this problem has been solved by libraries such as SwiftlyJSON, or ridiculously clever functional concepts.
The basic problem is as follows, when JSON is parsed, a dictionary is created at runtime, which is a collection of name-value pairs of Any
type:
Let’s say you want to create a strongly typed model:
You’re going to need this code:
In this case the pyramid is caused because the dictionary subscript (i.e. the []
part) returns an optional result, furthermore a failable cast, as?
, is required.
This can be replaced with an if_let
overload that uses the type information from the supplied function in order to perform the required cast:
Which is used as follows:
Again, removing the pyramid (here’s a Swift Stub). Note that in this instance you must provide type annotations for the closure variables, because this type information is used by if_let
in order to cast the values of type Any
returned by the dictionary.
This technique probably isn’t as elegant as SwiftlyJSON, but it’s an interesting alternative.
Conclusions
Hopefully you’ve found some useful techniques in this blog post that will help you with your own personal battles with optionals!
I’m sure there are more interesting extensions possible, perhaps using custom operators? Or how about the annoying cases where you want to perform an if-let and combine it with some other boolean logic?
If you come up with any good ideas, please share!
Regards, Colin E.