“We should do (as wise programmers aware of our limitations) our utmost best to … make the correspondence between the program (spread out in text space) and the process (spread out in time) as trivial as possible.”
—Edsger W. Dijkstra, “Go To Considered Harmful”
Swift 2.0 introduced two new control statements that aimed to simplify and streamline the programs we write: guard
and defer
. While the former by its nature makes our code more linear, the latter does the opposite by delaying execution of its contents.
How should we approach these new control statements? How can guard
and defer
help us clarify the correspondence between the program and the process?
guard
guard
is a conditional statement requires an expression to evaluate to true
for execution to continue. If the expression is false
, the mandatory else
clause is executed instead.
func factorial(_ n: Int) throws -> Double throws {
guard n > 0 else {
NSExeption.raise()
}
return (1...n).map(Double.init).reduce(1.0, *)
}
The else
clause in a guard
statement must exit the current scope by using return
to leave a function, continue
or break
to get out of a loop, or a function that returns Never
like fatalError(_:file:line:)
.
guard
statements are most useful when combined with optional bindings. Any new optional bindings created in a guard
statement’s condition are available for the rest of the function or block.
See how optional binding works with a guard-let
statement. Compared with if-let
statement we can avoid hierarchy of encapsulated expressions. Note: the unwrapped variable t
is available even after guard scope has been close.
class Node<T: Comparable> {
let value: T
var left: Node?
var right: Node?
init(value: T, left: Node? = nil, right: Node? = nil) {
self.value = value
self.left = left
self.right = right
}
}
func binaryTreeSearch<T: Comparable>(value: T, tree: Node<T>?) -> Bool {
guard let t = tree else {
return false
}
if t.value == value {
return true
} else if value < t.value {
return binaryTreeSearch(value: value, tree: t.left)
} else {
return binaryTreeSearch(value: value, tree: t.right)
}
}
Here before search is started a check is done if the current tree is not nil
– mean empty. In that case we immediately stop the search. Otherwise the tree variable is unwrapped and available in the remaining part
defer
Although the defer keyword was already introduced in Swift 2.0, it’s still quite uncommon to use it in projects. Its usage can be hard to understand, but using it can improve your code a lot in some places where you have to cleanup resources.
A defer
statement is used for executing code just before transferring program control outside of the scope that the defer
statement appears in.he most common use case seen around is opening and closing a context within a scope.
If multiple defer
statements appear in the same scope, the order they appear is the reverse of the order they’re executed.
func writeFile() {
let file: FileHandle? = FileHandle(forReadingAtPath: filepath)
defer { file?.closeFile() }
// Write changes to the file
}