How to Find the Kind of Errors a Method May Throw and Catch Them in Swift

How to find the kind of errors a method may throw and catch them in Swift

NSError automatically bridges to ErrorType where the domain becomes the type (e.g. NSCocoaErrorDomain becomes CocoaError) and the error code becomes the value (NSFileReadNoSuchFileError becomes .fileNoSuchFile)

import Foundation

let docsPath = "/file/not/found"
let fileManager = FileManager()

do {
let docsArray = try fileManager.contentsOfDirectoryAtPath(docsPath)
} catch CocoaError.fileNoSuchFile {
print("No such file")
} catch let error {
// other errors
print(error.localizedDescription)
}

As for knowing which error can be returned by a specific call, only the documentation can help. Almost all Foundation errors are part of the CocoaError domain and can be found in FoundationErrors.h (though there are some rare bugs where Foundation can also sometimes return POSIX errors under NSPOSIXErrorDomain) but these ones might not have been fully bridged so you will have to fall back on managing them at the NSError level.

More information can be found in « Using Swift with Cocoa and Objective-C (Swift 2.2) »

How get the list of errors thrown by a function?

When the Swift docs says a function throws, they mean that it throws an ErrorType (in Cocoa APIs usually an NSError), not an exception.

Consider the following do-try-catch flow for NSFileManager's createDirectoryAtPath:

let documentsPath = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)[0]

do {
try NSFileManager.defaultManager().createDirectoryAtPath(documentsPath, withIntermediateDirectories: false, attributes: nil)
} catch {
// 'error' variable automatically populated
print(error)
print(error.dynamicType)
}

createDirectoryAtPath will fail because the documents directory already exists. Logging the dynamicType of the error shows that it is in fact an NSError object:

Error Domain=NSCocoaErrorDomain Code=516 "The file “Documents” couldn’t be saved in the folder “35B0B3BF-D502-4BA0-A991-D07568AB87C6” because a file with the same name already exists." UserInfo={NSFilePath=/Users/jal/Library/Developer/CoreSimulator/Devices/E8A35774-C9B7-42F0-93F1-8103FBBC7118/data/Containers/Data/Application/35B0B3BF-D502-4BA0-A991-D07568AB87C6/Documents, NSUnderlyingError=0x7fa88bd14410 {Error Domain=NSPOSIXErrorDomain Code=17 "File exists"}}

NSError

In order to see the different types of errors a function can throw, you would have to examine the error for information to determine the type of error thrown, and how to handle each error. In the case of NSError this would be its domain, code, and description.

In this particular case, a directory already exists at that path, so the file manager cannot create a new directory. An example of another reason why this operation could fail would be if the file manager did not have write access. That would be error code 256.

Swift Catch Pattern that binds the error to a variable

The syntax used in a catch line is exactly the same pattern syntax used in the case of a switch. If you know how to write a case you know how to write a catch.

So, for example, you complain:

} catch let decodingError is DecodingError {  // THIS IS NOT VALID

Right. But this is valid:

} catch let decodingError as DecodingError { 

Oh what a difference one letter makes.

Swift do-try-catch syntax

There are two important points to the Swift 2 error handling model: exhaustiveness and resiliency. Together, they boil down to your do/catch statement needing to catch every possible error, not just the ones you know you can throw.

Notice that you don't declare what types of errors a function can throw, only whether it throws at all. It's a zero-one-infinity sort of problem: as someone defining a function for others (including your future self) to use, you don't want to have to make every client of your function adapt to every change in the implementation of your function, including what errors it can throw. You want code that calls your function to be resilient to such change.

Because your function can't say what kind of errors it throws (or might throw in the future), the catch blocks that catch it errors don't know what types of errors it might throw. So, in addition to handling the error types you know about, you need to handle the ones you don't with a universal catch statement -- that way if your function changes the set of errors it throws in the future, callers will still catch its errors.

do {
let sandwich = try makeMeSandwich(kitchen)
print("i eat it \(sandwich)")
} catch SandwichError.NotMe {
print("Not me error")
} catch SandwichError.DoItYourself {
print("do it error")
} catch let error {
print(error.localizedDescription)
}

But let's not stop there. Think about this resilience idea some more. The way you've designed your sandwich, you have to describe errors in every place where you use them. That means that whenever you change the set of error cases, you have to change every place that uses them... not very fun.

The idea behind defining your own error types is to let you centralize things like that. You could define a description method for your errors:

extension SandwichError: CustomStringConvertible {
var description: String {
switch self {
case NotMe: return "Not me error"
case DoItYourself: return "Try sudo"
}
}
}

And then your error handling code can ask your error type to describe itself -- now every place where you handle errors can use the same code, and handle possible future error cases, too.

do {
let sandwich = try makeMeSandwich(kitchen)
print("i eat it \(sandwich)")
} catch let error as SandwichError {
print(error.description)
} catch {
print("i dunno")
}

This also paves the way for error types (or extensions on them) to support other ways of reporting errors -- for example, you could have an extension on your error type that knows how to present a UIAlertController for reporting the error to an iOS user.

Catching an error from a non-throwing method

You can use the init(exactly:) constructor, it will not throw an error but it will return nil if the value is to large

guard let value = Int(exactly: pow(Double(1000000000), Double(10))) else {
//error handling
}


Related Topics



Leave a reply



Submit