Control Flow Changes: Swift 2.0 (Xcode 7 Beta)

Xcode 7 Beta 1 and Swift 2.0 – introduced four new control flow structures: doguarddefer, and repeat. These keywords let you execute code in a slightly different way than we did before in Swift 1.2. I was playing around with these and figured it’d be good to go over a few of the changes.

Cleaner Error Handling with Do/Try/Catch/Throws/Throw

Swift is on a mission to eliminate the Objective-C error handling method of having an error out parameter. This means that there are almost no reasons to pre-declare an error variable and pass that pointer down. The error will be thrown instead of returned. Compare the following two snippets:

Before

var error: NSError?
self.doSomethingThatMightFail(&error)
if error != nil {
  //..handle error
}

After

do {
  try self.doSomethingThatMightFail()
} catch error as NSError {
  //..handle error
}

As you can see with this example, the biggest change is that you don’t have to pass a pointer to the method as a parameter. This cleans up your parameter list, and it formalizes how the error should be handled.

For those who have not used exceptions before, remember to not abuse them. Errors are typically reserved for severe errors that are unexpected (hence ‘exception’) and that must be handled: document parsing, network failures, disk operations, failing to access system services such as CoreLocation, etc. They should not be used to highlight developer errors (such as missing arguments). Adding a method that throws an exception increases the developer pain when using the method. This directly reduces how ‘enjoyable’ it is to use your method, library, or what not.

If you are familiar with other languages that have exceptions, you are likely also familiar with how certain exceptions must be handled, even if you are certain that no exception will be thrown. Luckily, the Swift team predicted this and added what they are calling the forced-try. The forced-try “disables error propagation” or, in other words “let’s you avoid writing a useless catch block”. Here’s an example:

Before

do {
  try self.doSomethingSuperSafe()
} catch error as NSError {
  //..this shouldn't happen
}

After

try! self.doSomethingSuperSafe()

You should only ever do this if and only if you’re positive that “doSomethingSuperSafe()” will never throw an error. If it does, your app will have to deal with a runtime error which should almost always result in a crash. There are very few occasions where this is appropriate, but it’s a nice addition to the language.

Inverting Conditions and Saving Lines with Guard

guard statements are a formalization of how we write our guard statements (or early returns, if you may). I’ve always sat on the fence between ‘single return statement’ and ‘guard city’ style methods but since guards are now a part of the language, I think it’s a good indication that we should head in that direction.

Before (Single Return)

function banana(x: Int) -> Int {
  var result = 0
  if x > 0 and x < 100 {
    result = 50
  }
  return result
}

Before (Guards/Early Return)

function banana(x: Int) -> Int {
  if x < 0 || x > 100 {
    return 0
  }
  return 50
}

After (Guard Statements)

function banana(x: Int) -> Int {
  guard x > 0 && x < 100 else {
    return 0
  }
  return 50
}

In terms of the amount of code you have to write, it’s roughly the same. However, the “logic cleanup” that a guard statement offers is very nice.

  1. It makes it very clear what is good input versus what is bad input. This is a huge readability win, as opposed to a case where you have to read half of the method before figuring this out.
  2. Rather than having to do the inverse check (x < 10 || x > 100) instead of (x > 0 && x < 100), you use the actual check. Again, this makes it clearer what’s valid, and what’s not valid.

You can see more examples and information on guard statements in this article here.

Avoiding Pyramids of Doom with Defer

If you’ve ever written multi-threaded code, you’ve probably forgotten to release a mutex or signal a semaphore. Not because you forgot to write the code, but because you accidentally introduced a condition that would skip the release/signal. For example:

func banana(x: Int) {
  self.mutex.lock()
  if (x < 0) {
    return
  }
  else {
    //critical section
  }
  self.mutex.unlock()
}

The problem with this code block should be obvious: if x < 0, then the mutex will never unlocked. There are a million ways to clean up that specific example (i.e do the checks before) but the point here is that you have to be very careful that unlock is always called. This often produces really unwieldy code that somehow lead back to the unlock statement. This is especially difficult when the complexity of the method is high (if you want to measure complexity with the number of code branches a method has…).

In comes our knight in shining armour: defer. The defer statement, once executed, forces code to be executed when the current code block exits. This is extremely powerful. The Swift documentation gives a great example of how defer cleans up code. Here is how you would fix the above code block using defer.

defer

func banana(x: Int) {
  self.mutex.lock()
  defer {
    self.mutex.unlock()
  }
  if x >= 0 { //you should use a guard instead of this though... but this is just an example!
    //critical section
  }
}

Before using defer, there are a few things that you need to keep in mind:

  1. The block is guaranteed to execute on exit. This is awesome because you don’t have to write convoluted code paths to ensure code gets run.
  2. However, the block is only going to execute on exit. This is awkward because if you are using defer as a way to manage locks, then you effectively put the entire code block into the critical section. This is often overkill. You’ll probably have to re-arrange your code to avoid this, if you want to take advantage of defer.

Make Reading Code More Natural with Repeat

The simplest addition to understand is the repeat-while construct. I don’t really have to go over it, as the documentation covers it very well. In essence, it’s a do-while loop. It guarantees one execution before the condition is verified.

Before

var x = banana()
while (x < 0) {
  x = banana()
}

After

repeat {
  var x = banana()
} while x < 0

Nice and simple cleanup here.

For me, writing this was an exercise in learning what’s new in Swift. I think that these additions are really welcome and have an opportunity of standardizing how most people write swift and deal with these kinds of situations. Hopefully this was helpful!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

w

Connecting to %s