I have this universal error handler for my HomeKit app (currently in development). It grew out of wanting a single place to write error messages, show alerts, etc. I wanted a short, simple way to call it from anywhere that may generate an error.
Here's the class:
import UIKit
import HomeKit
class ESErrorHandler : NSObject
{
class func handleError(error: NSError) -> Bool
{
NSLog("Handling error \(error) in centralized handler")
let code = HMErrorCode(rawValue: error.code)
switch code! {
case HMErrorCode.FireDateInPast:
showAlertWithTitle("Invalid date", body: "Date must be in the future")
case HMErrorCode.AccessDenied:
showAlertWithTitle("Access Denied", body: "You don't have permission to access the specified item")
... // A bunch more cases
default:
NSLog("Couldn't handle error with code \(error.code)")
showAlertWithTitle("Error", body: "Couldn't successfully complete action")
return false
}
return true
}
class func showAlertWithTitle(title: NSString, body: NSString)
{
dispatch_async(dispatch_get_main_queue())
{
let alert = UIAlertView(title: title, message: body, delegate: nil, cancelButtonTitle: "OK")
alert.show()
}
}
}
I call it from other code like this:
homeManager.addHomeWithName(currentHouseName) {(home: HMHome!, error: NSError!) in
if error != nil
{
ESErrorHandler.handleError(error)
self.title = "Add Home"
}
else
{
... //Stuff worked, do other stuff
}
}
My concerns:
- Something feels wrong about putting using a class method for
showAlertWithTitle:body:
- I don't really like having using
switch code!
, it seems clumsy like I'm missing something in unwrapping
2 Answers 2
I don't really like having using
switch code!
, it seems clumsy like I'm missing something in unwrapping.
Yes, that will crash if the function is called with an error code
that does not correspond to one of the HMErrorCode
enumeration values.
Better use optional binding with if let ...
:
class func handleError(error: NSError) -> Bool
{
if let code = HMErrorCode(rawValue: error.code) {
switch code {
case HMErrorCode.FireDateInPast:
showAlertWithTitle("Invalid date", body: "Date must be in the future")
case HMErrorCode.AccessDenied:
showAlertWithTitle("Access Denied", body: "You don't have permission to access the specified item")
// Other cases ...
default:
NSLog("Couldn't handle error with code \(error.code)")
showAlertWithTitle("Error", body: "Couldn't successfully complete action")
return false
}
} else {
NSLog("Unknown error with code \(error.code)")
showAlertWithTitle("Error", body: "Unknown error \(error.code)")
return false
}
return true
}
It is also not obvious from your code why the function has a (boolean) return value, as the caller ignores it.
-
\$\begingroup\$ The boolean return value is so that other code could tell if the error was successfully handled. Thanks! \$\endgroup\$user25840– user258402014年10月25日 20:13:20 +00:00Commented Oct 25, 2014 at 20:13
Perhaps a cleaner implementation could work as follows.
Create individual error notification types as strings, ie:
let HMErrorAccessDenied = "HMErrorAccessDenied";
Register a common handler for all of your various error notification types in your application delegate.
NSNotificationCenter.defaultCenter().addObserver(self, selector:"universalErrorHandler:", name:HMErrorAccessDenied, object:nil);
Instead of tightly coupling your methods with an error handler (like your call to
ESErrorHandler.handleError(error)
inhomeManager.addHomeWithName()
) you emit error notifications viaNSNotificationCenter.postNotification()
and let the registered error handler take care of reacting. This leaves your code flexible in that adding additional handlers at a later date is very easy.Use the localization features of the Apple-provided frameworks in your handler to load error strings and display those in reaction to the error notifications.
This all assumes that the errors you'll be posting notifications about are things that the user needs to be aware of in some way (though a dialog or an update in the user interface). Other error types, such as recoverable errors, should be passed back to the calling code as either return values or inout parameters.