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
How to Unwind Through Multiple Views Without Displaying Intermediate Views
Programmatically Highlight Uibarbuttonitem
Xcode Error:Distill Failed for Unknown Reasons
#If Canimport() Does Not Find Frameworks with Cocoapods
Switching from Xcode3 to Xcode4 - Can't Load Programs Onto Older Ipod Touch
Swipe Left or Right to Load the View Controller with the Collection View Cell Highlight
Trigger a Method in Uiviewcontroller from Its View
How to Run Multiple iOS Simulators at Once
Running Nsurlsession Completion Handler on Main Thread
Setting Up a Plist to Store Application Data (Not Settings) for an iPhone Game
How to Read Incoming Sms by Using Application in iOS
Perform Segue from App Delegate Swift
What's Wrong Here: Instance Member Cannot Be Used on Type
Generate Rsa Public Key from Modulus and Exponent