Update, 07 Feb 2017
As of Swift 3, some of the code below may not work properly anymore. To see what changed, read Pyramid of Doom Updated (Swift 3).
Original Post
Swift 2 was announced in June, soon to be a year ago. Still, some of the concepts it introduced are new to many iOS developers – especially the ones who keep using ObjC as their #1 language and are only starting to learn Swift.
One of the new things introduced in Swift 2 is guard statement, which I think finally solves Swift’s pyramid of doom (or what I called “let indentation hell” – that’s what you get when you are nesting optional binding statements one inside another).
First, let’s take a look at hypothetical code of setting an author on a book, in Swift < 1.2
func letIndentationHell(foo: Book?, bar: Author?) {
if let book = foo {
if let author = bar {
book.setAuthor(author)
}
}
}
That was one of the things that really terrified me when I first started using Swift 1.0 – and when you were also using ObjC-bridged code, this nesting could have been even deeper.
Swift 1.2 allowed us to bind multiple optionals in one statement
func multipleBind(foo: Book?, bar: Author?) {
if let book = foo, let author = bar {
book.setAuthor(author)
}
}
That already looks better, but still, it forces you to move most of your function’s body inside indented blocks.
You could avoid it, but it would require ObjC-style nil
checking, and while it works, I just personally don’t like it as much – Swift was supposed to be better than ObjC, right?
func objcStyle(foo: Book?, bar: Author?) {
if (foo == nil && bar == nil) {
return
}
let book = foo!
let author = bar!
book.setAuthor(author)
// or force unwrap both directly with
// foo!.setAuthor(bar!)
}
Function above is longer than it could be and it’s also really easy to write wrong condition for the if statement – actually that’s what I did writing it for the first time for this post.
If you try the code above and one of the obj1
or obj2
is equal to nil
– you will get an error.
(proper condition is if (obj1 == nil || obj2 == nil)
)
What Swift 2 introduced is actually a combination of solutions suggested in example #2 and #3 – it’s called guard statement.
func swiftGuardStyle(foo: Book?, bar: Author?) {
guard let book = foo, author = bar else {
return
}
book.setAuthor(author)
}
Now, all the optionals can be checked at the beginning of your function and you can provide a code, that will be executed if the condition fails (inside the else
block).
Actually if you forget to provide the code for when condition fails, compiler will throw an error! Guard
must always have an else
clause, that needs to contain return
or break
inside.
This also allows you to name objects passed to your functions and to your closures in a sane way.
I used to name function arguments and local variables differently – so I could clearly see which variables are of optional type and which ones are already unwrapped.
In this case, instead of taking variables named foo
and bar
as function parameters, you could rewrite your code with book
and author
func swiftGuardStyle2(book: Book?, author: Author?) {
guard let book = book, author = author else {
return
}
book.setAuthor(author)
}
(this could have also been done in previous examples, but it feels much cleaner with guard
)
You can also combine guard
with where
, to check specific condition on your optionals AND – you can use it to check conditions on non-optional values!
(I found that not long ago on Eric Cerney’s post)
func swiftGuardStyleWhere(book: Book?, author: Author?, rating: Int) {
guard rating > 4, let book = book, author = author where author.name == "Bob" else {
return
}
// will only execute if book and author are non nil
// and when author's name is "Bob"
// and when rating is > 4
book.setAuthor(author)
}
With guard
, Swift programming becomes easier and your code becomes cleaner – you can now check for expected conditions, not for error case (like with foo == nil
) and as a bonus – you’re getting rid of that ugly indentation.
You can find all code examples from the post on GitHub.