Error Handling In Swift

Introduction

 
Errors are failures the programmer expects to happen during normal operation. This includes network issues and string entered is malformed. Exception handling is handling the runtime occurence of errors. The program will terminate the execution when the exception occurs.
 

Throwing and catching

 
When calling a throwing function, the code does not compile unless it is annotated with the try block. The try keyword signals that a function can throw an error both to the compiler and the reader. Calling the throwing function forces it to decide on the process if the error occurs. The error is handled by do or catch, or the error can be propagated by  the throw keyword. The catch block is used to catch the specific error type. Within the catch clause, the compiler automatically creates a variable named as error available.
  1. do {  
  2.     let result =  
  3.         try contents(ofFile: "input.txt")  
  4.     print(result)  
  5. catch FileError.fileDoesNotExist {  
  6.     print("File is not available")  
  7. }  
  8. catch {  
  9.     print(error)  
  10. }   
The pattern matching can be used to parse a string to diferentiate between the error cases. The line number and warning message is bound to the variables if the warning cases occur.
  1. do {  
  2.     let result =  
  3.         try parse(text: "{\"message\":\" Peace\"}")  
  4.     print(result)  
  5. catch ParseError.wrongEncoding {  
  6.     print("Encoding is wrong")  
  7. }  
  8. catch  
  9. let ParseError.warning(line, message) {  
  10.     print("Warning Error occur at the line \(line):\(message)")  
  11. }  
  12. catch {  
  13.     preconditionFailure("Unexpected Error occurs")  
  14. }   

Non-Ignorable Error

 
There is built in error handling in Swift. The built in error handling is used so that the compiler cannot ignore the error case when calling a funtion. The compiler forces us to prefix the call with try when calling the throw based variants. 
  1. extension Data {  
  2.     func write(to url: URL, options: Data.WritingOptions = []) throws  
  3. }  
  4. extension FileManager {  
  5.     func removeItem(at: URL) throws  
  6. }  
  7. // the dclaration is below when result based error handling used      
  8. extension Data {  
  9.     func write(to url: URl, options: Data.WritingOptions = []) - > Result < (), Error >  
  10. }  
  11. extension FileManager {  
  12.     func removeItem(at URl: URl) - > Result < (), Error >  
  13. }   

Chaining Errors

 
Multiple functions can be called in a row and every single subtask can fail with an error; if an error is thrown the entire process is stopped immediately. Swift built in error handling shines in this regard. The nested if statement is not needed to unwrap return values before passing them to the function. The error that occurs breaks the chain and switches control to catch the block.
  1. func complexOperation (filename : String) throws-> [String]    
  2. {    
  3.     let text = try contents (ofFile : filename)    
  4.     let segments = try parse (text : text)    
  5.     return try process (segments : segments)    
  6. }    

Chaining result

 
Chaining multiple functions that return a result is a lot of work when it is done manually. The function is called first and switches over its return value and the unwrapped value can be passed to the function. The two return failure lines convert the error value from its concrete type to Error and the compiler adds it implicitly. The flat map is readable without the mapError calls. The aggregation ended up erasing the concrete error type.
  1. func complexOperation1(filename: String) - > Result < > [String], Error > {  
  2.     let result1 = contents(ofFile: filename)  
  3.     switch result1 {  
  4.         case.success(let text): let result2 = parse(text: text)  
  5.         switch result2 {  
  6.             case.success(let segments): return process(segments: segments).mapError {  
  7.                 $0 as Error  
  8.             }  
  9.             case.failure(let error): return.failure(error as Error)  
  10.         }  
  11.         case.failure(let error): return.failure(error as Error)  
  12.     }  
  13. }   

Rethrowing

 
The throwing function poses a problem for function that takes other functions as arguments, such as map. The invalid files are filtered out when there is an array of files. The checkFile function either returns a boolean value or it throws an error if checking the file is wrong. The compiler will not accept any throwing function to be passed in as predicate because the isincluded parameter is not marked as throw.