Error handling is the process of responding to and recovering from error conditions in your program. In many situations, we may have to deal with errors. Sometimes we also have to let the co-workers know that the program throws the error. I will try to explain every aspect of error handling here in this article.
Some operations cannot guaranteed to always complete execution or produce a useful output. The optional can be used to represent the absence of a value, but when an operation fails.
As an example, consider the task of reading and processing data from a file on disk. There number of ways this task can fail, including the file not existing at the specified path, the file not having read permissions, or the file not being encoded in a compatible format. Distinguishing among these different situations allows a program to resolve some errors and to communicate to the user any errors it can’t resolve.
Swift Error Handling used for handling the failing conditions gracefully. An error can lead to runtime errors or changes in the flow of the program. We come across different kinds of errors in our projects:
- Logic Errors
- Type Conversion Errors.
- External Errors such as FileNotFound etc.
The brute force way to handle errors is by using if else statements where we check each and every possible error. But this can lead to bloated codes with too many nested conditions.
In Swift, Errors are just values of a certain type. Swift does not support checked exceptions.’
Swift Error Protocol
Error Protocol is just a type for representing error values that can be thrown. Swift requires you to create a custom Error type. Typically an Enum is used which conforms to the Error protocol. It is more or less empty. Hence you don’t need to override anything from them. It is a must for Error Handling and creating Error types.
Let’s create a basic enum which conforms to this Error Protocol.
1 2 3 4 |
enum UserDetailError: Error { case noValidName case noValidAge } |
Now let’s use this Error Type in our classes and functions.
throws and throw
If a function or an initializer can throw an error, the throws
modifier must be added in the definition itself right after the paratheses and just before the return type.
1 2 3 |
func userTest() throws -> <Return Type> { } |
The throws keyword would propagate the error from the function to the calling code.
Otherwise, a non-throwing function must handle the error inside that function’s code itself.
throw
keyword is used for throwing errors from the error type defined.
Let’s look at an example demonstrating throws and throw in a function:
1 2 3 4 5 6 7 8 |
func userTest() throws { if <condition_matches> { //Add your function code here } else{ throw UserDetailError.noValidName } } |
In Error Handling, guard let is useful in the sense that we can replace the return statement in the else block with the throwing error. This prevents too many if-else conditions.
Let’s look at it with the example below.
1 2 3 4 5 6 7 8 9 10 |
func userTest(age: Int, name: String) throws { guard age > 0 else{ throw UserDetailError.noValidAge } guard name.count > 0 else{ throw UserDetailError.noValidName } } |
Note: You cannot add the Error type after the throws keyword in Swift.
In the above code, if the condition in the guard let fails it’ll throw an error and the function would return there itself.
Let’s look at how to handle these errors.
Swift try, do-catch
In Swift, contrary to Java, do-catch
block is used to handle errors in place of try-catch.
Every function that has throws needs to set in the try statement since it has a potential error.
Swift try statement is executed only when it is inside the do-catch block as shown below.
1 2 3 4 5 |
do{ try userTest(age: -1, name: "") } catch let error { print("Error: \(error)") } |
The below image shows the output of the above program.
Alternatively, we can do this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
do{ try userTest(age: -1, name: "") } catch UserDetailError.noValidName { print("The name isn't valid") } catch UserDetailError.noValidAge { print("The age isn't valid") } catch let error { print("Unspecified Error: \(error)") } |
Throwing Errors in Initializers
We can add throws
in the initializer in the following way.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
enum StudentError: Error { case invalid(String) case tooShort } class Student { var name: String? init(name: String?) throws { guard let name = name else{ throw StudentError.invalid("Invalid") } self.name = name } func myName(str: String) throws -> String { guard str.count > 5 else{ throw StudentError.tooShort } return "My name is \(str)" } } |
Now to initialize the class:
1 |
var s = Student(name: nil) //compiler error |
Since the initializer is throwing errors we need to append try
keyword as shown below.
1 2 3 4 5 6 7 8 9 10 |
do{ var s = try Student(name: nil) } catch let error { print(error) } //prints //invalid("Invalid") |
Let’s call the class function on the object too as shown below.
Swift try, try? and try!
- Swift
try
is the most basic way of dealing with functions that can throw errors. - try? used to handle errors by converting the error into an optional value. If the function could return nil and we known Optionals can be nil in Swift. Hence for
try?
you can get rid ofdo-catch
block. try!
used to assert that the error won’t occur. It should be used when absolutely sure that the function won’t throw an error. Liketry?
,try!
works without a do-catch block.
1 2 |
var t1 = try? Student(name: nil) var t2 = try! Student(name: "Anupam") |
Conclusion
So if you have any comments, questions, or recommendations, feel free to post them in the comment section below!