Ideally, errors should never occur. File we need should always be available and networks should always be present and reliable. Unfortunately this reality is not ideal and we have to deal with the consequences. Thankfully the Swift team included a great way to handle these deviations from the ideal in Swift 2. Swift Error Handling lets us quickly and easily warn the compiler whether a function can throw an error, actually throw it, and handle it in whatever way seems appropriate.
There is one major difference between how Objective-C handles errors, and the new Swift Error Handling. If you done any work with NSError in Objective-C, be honest, have you ever given “nil” instead of an NSError pointer when you dealt with a method that could throw an error? Yeah, I thought so. In Objective-C, to PROPERLY deal with errors, you would have to do something like this:
NSError *err = nil; NSString *fileStuff = [[NSString alloc] initWithContentsOfFile:@"someFile.txt" encoding:NSUTF8StringEncoding error:&err];
What this does is create an empty NSError object, which you then pass into that initializer with the ampersand ( & ) character, which is an operator to tell it to pass its memory address. Then, if there is an error, the called function or initializer would then find the spot in memory referred to by that address, place the appropriate NSError in it, and return from the function. This was a way to have multiple values returned from a function, by leaving an address where the new value could be placed. It was carried over to Swift as the “inout” keyword in a function prototype.
Now, if there was an error, you could look into that “err” variable and see which one it was and deal with it appropriately. But, if you didn’t care about the errors, you could just pass in the value “nil” directly, instead of the error pointer (the &err), and then any NSError thrown was just dropped into the oblivion that is nil.
In Swift with its new error handling syntax, dealing with errors is very explicit. Functions make it really obvious that they can throw an error, and your program must acknowledge such, and deal with it appropriately (or very explicitly ignore it).
Creating a Swift Error
How’s that for a weird title, actually creating an error? Well, actually we are creating an entity that will represent what error actually occurred in the program. Let’s say this app needs to read from a file, but there are a few different things that could happen when it tries. Everything could work fine, of course, or the file might not even exist. If it exists, the user might not have sufficient permissions to read it, or the file could have been damaged or corrupted somehow. For a function that will read this file, let’s create a Swift Error to represent those options:
enum FileReadingError: Error { case FileNotFound case InsufficientPermission case FileCorrupted }
The easiest way to create a Swift Error is to create an enumeration that conforms to the ErrorType protocol, as shown above. Then, you make cases to represent the different error conditions. Technically anything can conform to the ErrorType protocol, so you COULD use a struct or a class, but I personally can’t think of a very good reason to. The enum cases are a PERFECT way to represent a limited number of possible errors by name. They are made even better with the ability to have associated values, such as having the InsufficientPermission showing what permission level the current user is. If you want to read more about enumerations, check out the post Enumerations in Swift.
Now, let’s create a function that can throw this error:
func pretendToReadTestFile() throws -> Data { throw FileReadingError.FileNotFound }
Okay, this doesn’t ACTUALLY return anything and automatically throws the error, but we’re just looking at the mechanics of how to do it, not actually write a function to read a file and return its data.
Firstly, you have to mark the method as able to throw an error. That is simply done by using the “throws” keyword after the arguments, but before the arrow ” -> ” used to denote the return type. Then, inside the function, to throw the error, you simply type “throw” and the enumeration case that you want to send as the error. That’s it!
To actually call a function that throws, all you need to do is type the keyword “try” before the function call:
let data = try pretendToReadTestFile()
Handling Swift Errors
There are 4 main ways to handle Swift Errors:
Make Someone Else Do It
The first way is to not handle it at all and make someone else do it. For this one, you mark that your method that calls the throwing function itself is a throwing function. Then, whatever calls this new function will have to handle it. Eventually SOMETHING has to handle the error properly, but not necessarily that which calls the throwing function. For instance, if we had a File Management object that handles multiple aspects of file management like reading and writing, we might just want to punt that error up to whoever called the file manager to instead of handling it there.
To do this, just mark the calling function with the “throws” keyword. You still have to mark the function call to the actual function that might throw an error with try. If you want to store the return from a throwing function, you just call the function and store its data like normal, but you place the “try” between the equals sign and the function call.
func getDataFromSomething() throws -> Data { let data = try pretendToReadTestFile() return data }
Handle Specific Swift Errors
This way will probably look the most familiar to those that use exception handling in other languages. Swift Error handling is significantly different from exception handling though, and much more faster. Throwing a Swift Error is more like an alternative return statement, at least as far as how we use it: Instead of returning the intended value from a function call, it returns the appropriate Swift Error.
Basically you wrap the call to the throwing function in a “do” statement. Then after that you make “catch” statements, kind of like a Switch’s “case” statement, or an else-if for an if-statement, for the Swift Errors you are interested in, like so:
do { let someData = try pretendToReadTestFile() } catch FileReadingError.FileNotFound { print("The file was not found.") }
All the “do” block does is contain the code that calls the throwing function and diverts it to the appropriate catch statement if an error is thrown, VERY much like a switch statement and its cases. Also, you might not know of all of the possible errors that can be thrown, so we also have something equivalent to the “default” case in the Switch statement, or the “else” in an if-statement, which is just “catch” without a specific Swift Error mentioned:
do { let someData = try pretendToReadTestFile() } catch { print("Something weird happened.") }
Make throwing function’s return Optional
If you don’t care what the error is, but just need to know either the returned value, or to know that there IS no value to return, that sounds
like a job for Swift Optionals. This way, even if the return type isn’t optional, you basically tell the compiler “If there was an error, I don’t care what it is, just set the value to nil”. You call this with a question mark after the “try” keyword like this:
let possibleData = try? pretendToReadTestFile() //possibleData now contains "nil"
It is up to you when to handle specific errors with the do-catch statement or to use this style. If you really don’t need the reason, or the reason is rather obvious, using “try?” might just be the right tool. If you are making a network call, does it really matter to the user of your app if there was an error for a bad URL, a lost connection, or the host wasn’t found? It might, but for some apps it might not, all 3 of those mean you don’t have data you were trying to get, and “nil” may suffice to tell your code what to do since it doesn’t have the data it was requesting.
Assure the compiler it won’t throw an Error
If there’s a version with a question mark, you can bet there’s a version with an exclamation point. If you use “try!”, much like forced unwrapping of an optional, you call the throwing function, and if it DOES throw, your app will crash. If you are POSITIVE the call won’t throw, you can use this one. Apple’s iBook suggests one reason may be when using a throwing function to read a file contained in your apps bundle. Since it comes with the app, it will always be there, so you should never see a “File Not Found” or similar error. If you do, well, there are probably bigger problems to worry about.
In the case of our function that always throws an error, using this will cause a crash of course, but for an example of how to use it (and to see what the crash looks like):
let definitelyNoData = try! pretendToReadTestFile() //error: 'try!' expression unexpectedly raised an error: FileReadingError.FileNotFound
Conclusion
I am quite happy with how the Swift Team implemented error handling in Swift. It is a rather familiar way to handle it on the surface, but under the hood it is much faster, and less destructive than its cousin “Exception Handling”. No jumping up the call stack, just a different return telling us the nature of an error.
I hope you found this article helpful. If you did, please don’t hesitate to share this post on Twitter or your social media of choice, every share helps. Of course, if you have any questions, don’t hesitate to contact me on the Contact Page, or on Twitter @CodingExplorer, and I’ll see what I can do. Thanks!