First of all, we need to clarify the two concepts: exception and error. In Cocoa development, exception is often caused by mistake of programmer. Take some examples: when we send a non-existing message to an NSObject
object, an NSInvalidArgumentException
will be thrown with an error message "unrecognized selector sent to instance"; If we use an index out of range of NSArray
to access its element, we will get NSRangeException
. These issues should be resolved during app development process instead of leaving them to a real product. On the other hand, the "errors" are more reasonable and a part of users using your app. It could be unmatched user name and password when login, or try to create an image from a local file which is not image.
Obviously, errors in app are unavoidable and normal. Some other languages prefer to throw and catch exceptions for error handling. But in Cocoa, there is a distinct line between exception and error. It is not popular to handle error with throwing/catching exceptions, since you need to do extra setting to enable the exception in Build Setting and use try catch
to catch exceptions in an Objective-C context. More ever, in Swift, the try catch
is totally removed from the language.
We are going to focus on error handling in this tip. It is very important to design a good error handling flow. It is not only an API pattern consideration, but also has an influence on user experience.
Generally speaking, we can follow the same way in Objective-C, generating and passing NSError
object here and there, and check whether it is nil
or not to find out how things going on. Almost all APIs in Cocoa framework are in this approach. For a caller who expects the possibility of getting an error, an NSErrorPointer
could be passed into the method as a parameter. After the method returns, we can read the content of this pointer to ask whether the error happens or not. An example:
let path = "some_file_path"
var removeError: NSError?
let removed = NSFileManager.defaultManager()
.removeItemAtPath(path, error: &removeError)
if !removed {
if let error = removeError {
print(error.localizedDescription)
}
}
Sometimes instead of using an API with error output, we will supply this kind of method to others. Since the input NSErrorPointer
is a typealias
of a mutable unsafe pointer, we can directly set the NSError
object to the memory
property of the input pointer:
func doSomethingParam(param:AnyObject, error: NSErrorPointer) {
//...do something, if succeeded, let `success` be true...
let success = //...
if !success {
if error != nil {
error.memory = NSError(domain: "errorDomain",
code: 1,
userInfo: nil)
}
}
// ...
}
The biggest benefit by doing so is keeping compatibility with Objective-C. At the same time, it is a traditional way in Cocoa development. We can use this kind of APIs in both Objective-C and Swift.
As Swift brings some new features, if we do not need to consider the compatibility for Objective-C, we have some new choices in error handling. One of them is described in tuple, by changing a single return value to a tuple with NSError
.
func doSomethingParam(param:AnyObject) -> (Bool, NSError?) {
//...do something, if succeeded, let `success` be true...
if success {
return (true, nil)
} else {
return (false, NSError(domain:"errorDomain", code:1, userInfo: nil))
}
}
There is another common way to deal with error - using enum
. As an important feature of Swift, enum type could be bound to other instances. We can return an enum type in a method, in which define the success or error state.
enum Result {
case Success(String)
case Error(NSError)
}
func doSomethingParam(param:AnyObject) -> Result {
//...do something, if succeeded, let `success` be true...
if success {
return Result.Success("成功完成")
} else {
let error = NSError(domain: "errorDomain", code: 1, userInfo: nil)
return Result.Error(error)
}
}
And we can use switch
and let
to take the result data from enum:
let result = doSomethingParam(path)
switch result {
case let .Success(ok):
let serverResponse = ok
case let .Error(error):
let serverResponse = error.description
}