Error handling is one of the fundamental concepts of programming. A program without error handling is like a country without a justice system. Your app will behave in an unruly manner with no one to bring order to the program.Any programmer worth his salt should handle errors in his/her code.
Swift 2.0 includes a great error handling mechanism. They even changed the do-while loop construct to support the do-try-catch error handling construct and changing the do-while to repeat-while. While I am not against this construct, I think they could have kept the do-while construct and added repeat-until while they are at it and may be used just maybe-try-catch construct for error handling. But I am sure they have really good reasons to do what they have done.
Anyways so coming back to the error handling, the do-try-catch construct allows you to bring some law and order to your code. You can visualise it as part of the code that has the potential to do something bad and so in order to protect the sanctity of your code you would catch them if they ever try to do something bad.
In its simplest form the error handling construct is:
[code lang=”js”]
do {
let loot = try robbingTheHouse()
}
catch () {
arrestTheBurglar()
}[/code]
The functions that you call inside the construct should have the potential of doing something bad. You indicate that a function can do something bad by using the throws keyword. Its basically a way of proclaiming that the function has the potential to do something bad and so you better try and catch it whenever you call it.
So the function robbingTheHouse() would be defined as:
[code lang=”js”]
func robbingTheHouse() throws -> Loot?
{
return Loot(money:100, gold:500)
}
//Loot is a custom data type that defines the loot.
[/code]
We will not go into the details of those in this article but for clarity you could consider Loot as a struct with money and gold properties.
You could read this as a function that either throws an error or returns a Loot? instance but not both. Appreciate the placement of the throws keyword in the function definition.
Enhancement for Swift 3.0: It would have been great if Swift supported declaring what type of errors a function could throw so that the caller could know what kind of errors to catch. When there are a lot of error types and lot of functions that throws different kinds of errors it will be very useful to know what kind of errors a function could throw. Can also enforce stronger error type checking in the catch blocks.
Tangent: When a throwing function throws an error there is no return value in a true sense, however you can get a nil value as a return value even when the function throws by using try? or try! instead of just try when calling the function.
Next you need to define what value types the function can throw. The throwable error types in Swift should conform to the ErrorType protocol. Its an empty protocol that does not define anything except that it tags any type that conforms to it as a throwable data type.
A hypothetical enum data type can be defined as follows:
[code lang=”js”]
enum AlarmType : ErrorType
{
case OpeningTheGate
case WakingTheDog
case TrippingTheSecurityAlarm
case WakingTheOwner
}[/code]
Now you have a function that throws and a type that you can throw. You now have to actually thrown an error from that function. A function can throw an error by using the throw keyword followed by an value that conforms to the ErrorType protocol. E.g. AlarmType.WakingTheDog.
[code lang=”js”]
func robbingTheHouse() throws -> Loot?
{
if gateNotOiledProperly() {
throw AlarmType.OpeningTheGate
}
if steppedOnASqueakyDuck() {
throw AlarmType.WakingTheDog
}
if pickedTheFrontDoor() {
throw AlarmType.TrippingTheSecurityAlarm
}
if didWakeUpTheOwner() {
throw AlarmType.WakingTheOwner
}
let loot = robValuables()
return loot
}[/code]
NOTE: The functions used inside robbingTheHouse() are hypothetical functions that either return true or false based on whether the burglar has succeeded in that task or not.
Now you have everything to write good error handling code.
[code lang=”js”]
do{
let loot = try robbingTheHouse()
}
catch
{
print(“Burglar was caught robbing the house”)
arrestTheBurglar()
}[/code]
The example above catches all kinds of errors. Let’s make it a bit more interesting by catching specific errors and acting on them accordingly. You can catch specific errors by using multiple catch blocks and specifying the type of the error that each catch catches.
[code lang=”js”]
do
{
try robbingTheHouse()
}
catch (AlarmType.OpeningTheGate) {
turnOnTheLawnLights()
}
catch (AlarmType.WakingTheDog) {
turnOnTheLawnLights()
releaseTheDog()
}
catch (AlarmType.TrippingTheSecurityAlarm) {
turnOnTheLawnLights()
releaseTheDog()
callPolice()
}
catch (AlarmType.WakingTheOwner) {
shootTheBurglar()
callPolice()
}[/code]
You could also use single catch() statement and inside it use a switch statement on a local variable error that contains the error thrown by the function.
[code lang=”js”]
do
{
let loot = try robbingTheHouse()
escapeWith(loot);
}
catch
{
let alarmType = error as! AlarmType
switch (alarmType)
{
case .OpeningTheGate:
print("Burglar was caught opening the gate")
case .WakingTheDog:
print("Burglar was caught waking the dog")
case .TrippingTheSecurityAlarm:
print("Burglar was caught tripping the security alarm")
case .WakingTheOwner:
print("Burglar was caught waking the owner")
}
}[/code]
You now have a complete code that not only catches burglar trying to rob a house but also catching the burglar if he gets caught and also how get got caught.
Ignoring the Errors
Sometimes you might just want to ignore the errors thrown by a throwing function but are interested only in the non-failing execution of the function. In such cases writing a non-throwing version of that function is a very bad idea. So how do you call a throwing function without a do-try-catch block. Have you tried try?. I mean seriously try?
The try? statement lets you call a throwing function without the enclosing do-catch block. In this case if the function throws an error, then it just returns nil. The return value if any is a optional value
[code lang=”js”]
let loot = try? robbingTheHouse()[/code]
When you know that a throwing function will not thrown an error say for a particular condition then you can use the implicitly unwrapped version of the try statement written as try!. For instance if a burglar knows that a house does not have gates, dogs, security and the owner then he can simply call try! robbingTheHouse() and get away with a loot. Not a good practice, Isn’t it?
In conclusion, Swift’s do-try-catch is a great way to handle errors in a simple, elegant and efficient manner and when correctly used will provide high quality, readable and manageable code. So if you use Swift please do try catching the errors in your code!