Swift 2: Test Driving the Error Handling

I’d admit it, when Chris Lattner explained the new Swift 2 error handling code on Tuesday at WWDC (session available here), I was a bit overwhelmed. It looked pretty alien, Java-like, and unnecessarily complex. I felt like I’d have to sit down for a while to try to understand it.

However, yesterday morning for my Alt Conf talk, I had to change one of my code samples to Swift 2 code, with error handling included. While I did have to reference the slides from the WWDC session, the implementation turned out to be very easy, clean and natural!

The example I used was a very simple form with a name and an age text fields:

Simulator Screen Shot Jun 11, 2015, 6.46.59 AM

The implementation is as follows:

Model

The model is a simple Person value:

struct Person {
    let name: String
    var age: Int
}

View Model

To mitigate between the messiness of the data entry in the form and the clean model, I created a View Model:

struct PersonViewModel {
    var name: String?
    var age: String?
}

Since the view model is aware of the latest name and age data entry, it’s the perfect place to actually create the Person model. The problem of course, is that there are a few things that could go wrong before the model can be created. First, the user might not fill out all the required fields (name and age), or the age might be formatted wrong. This is where the error handling comes in! I’ve defined my Error Types as follows:

struct PersonViewModel {
    var name: String?
    var age: String?

    enum InputError: ErrorType {
        case InputMissing
        case AgeIncorrect
    }
}

Finally, I can create the person using the new Swift 2 error handling syntax:

struct PersonViewModel {
    var name: String?
    var age: String?

    enum InputError: ErrorType {
        case InputMissing
        case AgeIncorrect
    }

    func createPerson() throws -> Person {
        guard let age = age, let name = name 
             where name.characters.count > 0 && age.characters.count > 0 
        else {
            throw InputError.InputMissing
        }

        guard let ageFormatted = Int(age) else {
            throw InputError.AgeIncorrect
        }

        return Person(name: name, age: ageFormatted)
    }
}

Things I love about this:

  • I don’t have to return an optional!
  • the guard is super clean looking and easy to understand
  • I can put multiple conditions in my guard statement – I unwrap the age and name at the same time, since they produce the same error in this case

ViewController

Finally, I can easily catch the error in my ViewController when the user taps the Submit button:

import UIKit

class ViewController: UIViewController, UITextFieldDelegate {

    @IBOutlet weak var nameTextField: UITextField!
    @IBOutlet weak var ageTextField: UITextField!

    var personViewModel = PersonViewModel()

    override func viewDidLoad() {
        super.viewDidLoad()
    }

    func textFieldDidEndEditing(textField: UITextField) {
        personViewModel.name = nameTextField.text
        personViewModel.age = ageTextField.text
    }

    @IBAction func onSubmitButtonTap(sender: AnyObject) {
        view.endEditing(true)

        do {
            let person = try personViewModel.createPerson()
            print("Success! Person created. \(person)")
        } catch PersonViewModel.InputError.InputMissing {
            print("Input missing!")
        } catch PersonViewModel.InputError.AgeIncorrect {
            print("Age Incorrect!")
        } catch {
            print("Something went wrong, please try again!")
        }
    }

}

I love how simple and easy-to-read the error catching is!

Enjoy the article? Join over 14,500+ Swift developers and enthusiasts who get my weekly updates.

  • Kevin DeLeon

    Great example. I’m a bit new to iOS development, so I’m a bit confused why you need/would want an intermediary like PersonViewModel…why not just put that functionality in the Person model itself?

    • MVVM – Model View ViewModel pattern. But in a short, for example, you can use your Person model in another situation where you don’t want to create a person. You can use a Person just for reference.

      • Kevin DeLeon

        Ah, gotcha. I haven’t worked much in the MVVM pattern (mostly MVC within several frameworks), but that makes sense.

    • @kevin_deleon:disqus +1 for what @ricardopereiraw:disqus said. The main thing is that you want your model to be clean – have a name as a constant and the age as an Int for example to use in the rest of your code, but the data that comes in from a form (or an API), is not clean. So a ViewModel is a nice place to keep the “dirty” data and sanitize it for the model. Also check out Jesse Squires blog post on doing something similar for the API Layer: http://www.jessesquires.com/swift-failable-initializers-revisited/.

  • Pingback: Michael Tsai - Blog - WWDC 2015 Links()

  • DrFecher

    Great post, Natasha! Very much agreed that the error catching in Swift 2.0 is a bold step forward. Related, I just posted about the new ‘guard’ statement and why I think it is an awesome step forward: http://matthewfecher.com/app-developement/swift-2-0s-new-take-on-defensive-design-with-guard/

  • wottpal

    Thanks for sharing this, couldn’t agree more. I especially fell in love with the defer-statement, I was always creating a closure at the beginning of my methods containing all the cleanup/finishing code and called that at every exit point… Now that is so much cleaner!

    • Wouldn’t it make more sense to put it at the end of the method, like a “else” statement. Like…

      func doSomthing() {
      //something
      } always {
      //the deferred closure
      }

      Logically, defer means “do this after everything else”, so it seems rather odd that it’s both BEFORE everything else, as well as inlined.

      • wottpal

        This is a semantically good idea, but with inline defer-statements you can place as many as you want as well as only if a certain condition is met:

        func burger(veg: Bool) {
        defer { print(“Cheese”) }
        if !veg {
        defer { print(“Ham”) }
        }
        }

        • func burger(veg: Bool) {
          //dosomething
          } defer {
          if !veg { print(“Ham”) } else { print(“Cheese”) }
          }

          or better yet, following the logic of practically every other system:

          func burger(veg: Bool) {
          //dosomething
          } defer !veg {
          print(“Ham”)
          } defer {
          print(“Cheese”)
          }

        • func burger(veg: Bool) {
          //do stuff
          }
          defer {
          if !veg {
          print(“Ham”)
          } else {
          print(“Cheese”)
          }
          }

          Functionally identical, yet far easier to understand IMHO.

  • Tan Man

    are you not getting an error passing an Int in place of String -> return Person(name: name, age: ageFormatted)

  • I hate it. There are generally two types of errors, those you can do something about, and those you can’t. The later are useless to TRY, because what exactly are you going to do if you get an IndexOutOfRange? But in your examples, something like AgeIncorrect, is simple to correct. And those are much more easily handled by returning an optional parameter, with the main result being null.

  • Vinayak Badrinathan

    Is there any way to get the details of what actually went wrong? Especially in the case where the method returning the NSError is implemented in obj-c and we’d like to print the error description…

    • Yes, they showed it in the WWDC Video: https://developer.apple.com/videos/wwdc/2015/?id=106. Basically, the last catch should just be changed to “catch let error”, and you’ll be able to use the error.

      • Vinayak Badrinathan

        Oh great, guess I missed that! Thank you!

      • Leonardo Faoro

        catch is by default defined as catch let error, you may use the error variable directly without pattern matching.
        } catch {
        print(error)
        }

        • I hope they change it to catch let error in the future. Not comfortable with just catch here.

  • This example is a nice illustration of how error handling works in swift 2, but if there’s anything to be learned from Java, it’s that you shouldn’t use exceptions for non-exceptional behavior.

    Input validation is easily handled and tested through normal control flow. Throwing errors should be a rarity in your code. Guarding and catching everywhere is just an obfuscation.

  • Dhaval

    i am having a compiler error in ErrorType

  • Xavi Matos

    i feel like the first guard clause in PersonViewModel.createPerson() ought to have the following where clause


    guard let age = age,
    let name = name
    where name.characters.count > 0 && age.characters.count > 0
    else {
    throw InputError.InputMissing
    }

  • David Waite

    @cpickslay:disqus, Swift does not have exceptions, except where they leak in from Objective-C and from C++. It is gaining the idea of error results as a language feature.

    @natashatherobot:disqus, one challenge with your sample is using errors to report *both* fields are invalid. Perhaps case MultipleErrors([InputError]) ?

  • Jack Ngai

    Thanks Natasha for this post. I’m a beginner at Swift and I noticed I don’t use do-try-catch-throw in any of my code so I’m trying to learn to incorporate it whenever I can.

    I ran your code and it seems like it is missing delegate assignment for both text fields as I was getting “input missing” every time I tap the submit button.

    I added them in the viewDidLoad method.
    nameTextField.delegate = self
    ageTextField.delegate = self

    Please ignore if this is a mistake on my end. I’m looking forward to reading more posts on Swift 3 from you.