This will be a shorter post compared to some of my previous ones, but I
wanted to share three useful guard
tips for structuring your functions
in such a way that you end up with code that is more concise and also
easier to understand. This is not a post about general coding
styles or coding guidelines, but more about how guard
can help you
simplify your code.
Binding and Condition Combination
Nesting
The first example concerns the use of pattern matching in order to
let bind variables into the current scope. One thing I really like
about this syntax (compared to, say if let
) is that it keeps a golden
code path, guarding you from the all-too common skyscraper of
death. Compare:
// Lots of nonsensical code to show how nested code structures look confusing
if let a = a
with:
guard let a = a else
let x = b
x.fn
guard let u = x.nxt else
let ux = u.brm
guard let uxt = ux.nxt else
perform
Now these are awful examples of how not to structure an internal API,
but they exist more to drive a point home. Guard
is great because it
binds the result into the current scope instead of the nested scope. In
larger functions, this makes all the difference between having huge,
difficult-to-grasp deeply-nested functions and clean versions which look
almost like lists of commands.
Pattern Binding
The above works even better, if your input is an enum
. Consider how
we're handling the following usecase:
protocol NotificationListener if user == self.currentUser
}
}
The binding in the guard case
line achieves two things for us:
- It makes sure
handleNotifications
only works forfileUploaded
notifications, and not foruserLoggedIn
notifications. - It binds all the
associated values
of the enum into the current scope, making it easy for us to use the data.
Where Clauses
However, with the power of guard
, we can even simplify the example. Lo
and behold:
moveFile
}
}
Now, the code is even shorter as the where
clause of the guard
expression does the correct matching for us.
You can have multiple where
clauses in your guard
statement:
import Foundation
func confirmPathprint
As you can see here, we're combining multiple let
bindings with
related where
clauses which makes it easy to handle all the
preconditions in one bigger guard statement instead of having to break
it up into multiple singular statements.
Nested Enums
The above even works for nested enums. This may sound like a far-fetched example, but I do actually have a project where I'm using a nested enum. In this example, we have a list of different items in the sidebar of an Instagram client. Those can be headlines, seperators, or folders:
A sidebar could be defined by an array like this:
Here, each Item
would have to have a different action: I.e. clicking
"Dashboard" should do something different compared to clicking
"Pictures", or the "Wedding" folder. The solution I chose was to
have another, nested, enum within the Item
enum:
Now, if we want publish a folder (to the cloud) we'd like to really make sure that we were called with a folder and not a headline or a Popular item:
func publishFolder
This is a great way to model complex hierarchies but still be able to match even intricate, nested types.
One-Line Guard Return
This is a short one. When you end up in the else
case, you may want to
perform an action before you return:
guard let a = b else
// or
guard let a = b else
As long as your command returns void
, you can actually combine these
into one:
guard let a = b else
// or
guard let a = b else
I find this much easier on the eyes and better to read. However, it may reduce readability in a complex project when another developer runs into this and wonders what kind of type is being returned here.
Alternatively, you can also use the semicolon in these cases1:
guard let a = b else
try?
in guards
Finally, in cases where you'd need to perform a throwable function, and
you don't care about the error result, you can still happily use
guard
just by utilizing the try?
syntax, which converts the result
of your throwing call into an optional, depending on whether it worked
or not:
guard let item = item,
result = try? item.perform
else
The neat thing about this is that it allows us to combine various Swift mechanics into one safe call to make sure that our code can safely proceed.
Wrapping Up
Everything combined into one long example. This also shows how you can
combine case
and let
in one guard
.
guard let messageids = overview.headers,
let messageid = messageids.first,
case .MessageId = messageid
where msgid == self.originalMessageID
else
That's it. For more detailed information, I recommend reading my much larger articles on pattern matching and enum.
After leaving Objective-C behind, you'll probably have to search your keyboard to find the key for it again ;)