Error Handler based on localized & healable (recoverable) errors without the overhead of NSError (which you would have when using LocalizedError & RecoverableError instead).
Why use MungoHealer?
When developing a new feature for an App developers often need to both have presentable results fast and at the same time provide good user feedback for edge cases like failed network requests or invalid user input.
While there are many ways to deal with such situations, MungoHealer provides a straightforward and Swift-powered approach that uses system alerts for user feedback by default, but can be easily customized to use custom UI when needed.
tl;dr
Here’s a very simple example of basic error handling without MungoHealer:
func login(success: (String) -> Void) {
guard let username = usernameLabel.text, !username.isEmpty else {
let alertCtrl = UIAlertController(title: "Invalid User Input", message: "Please enter a username.", preferredStyle: .alert)
alertCtrl.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
viewController.present(alertCtrl, animated: true, completion: nil)
return
}
guard let password = passwordLabel.text, !password.isEmpty else {
let alertCtrl = UIAlertController(title: "Invalid User Input", message: "Please enter a password.", preferredStyle: .alert)
alertCtrl.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
viewController.present(alertCtrl, animated: true, completion: nil)
return
}
guard let apiToken = getApiToken(username, password) else {
let alertCtrl = UIAlertController(title: "Invalid User Input", message: "Username and password did not match.", preferredStyle: .alert)
alertCtrl.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
viewController.present(alertCtrl, animated: true, completion: nil)
return
}
success(apiToken)
)
Using MungoHealer the above code becomes this:
func login(success: (String) -> Void) {
mungo.do {
guard let username = usernameLabel.text, !username.isEmpty else {
throw MungoError(source: .invalidUserInput, message: "Please enter a username.")
}
guard let password = passwordLabel.text, !password.isEmpty else {
throw MungoError(source: .invalidUserInput, message: "Please enter a password.")
}
guard let apiToken = getApiToken(username, password) else {
throw MungoError(source: .invalidUserInput, message: "Username and password did not match.")
}
success(apiToken)
}
)
MungoHealer is based on Swifts built-in error handling mechanism. So before we can throw any useful error message when something goes wrong, we need to define our errors.
MungoHealer can deal with any errors thrown by system frameworks or third party libraries alike, but to make use of the user feedback automatics, you need to implement one of MungoHealers error type protocols:
BaseError
A localized error type without the overhead of NSError – truly designed for Swift. Use this for any error you want to provide localized user feedback for.
Requirements
source: ErrorSource
A classification of the errors source. MungoHealer will automatically provide an alert title based on it. The available options are:
errorDescription: String
A localized message describing what error occurred. This will be presented to the user as the alerts message by default when the error occurs.
debugDescription: String?
An optional message describing the error in more technical detail for debugging purposes. This will not be presented to the user and so for only logged.
Example
struct PasswordValidationError: BaseError {
let errorDescription = "Your password confirmation didn't match your password. Please try again."
let source = ErrorSource.invalidUserInput
}
FatalError
A non-healable (non-recoverable) & localized fatal error type without the overhead of NSError – truly designed for Swift. Use this as an alternative for fatalError and tasks like force-unwrapping when you don’t expect a nil value and therefore don’t plan to heal (recover from).
Note that throwing a FatalError will crash your app, just like fatalError() or force-unwrapping nil would. The difference is, that here the user is first presented with an error message which is a better user experience. Additionally, before the app crashes you have the chance to do any cleanup or reporting tasks via a callback if you need to.
It is highly recommended to keep the suffix FatalError in your custom errors class name to clearly communicate that throwing this will crash the app.
Requirements
FatalError has the exact same requirements as BaseError. In fact its type declaration is as simple as this:
public protocol FatalError: BaseError {}
The only difference is semantics – the data provided by a FatalError will be used for alert title & message as well. But confirming the alert will crash the app.
Example
struct UnexpectedNilFatalError: FatalError {
let errorDescription = "An unexpected data inconsistency has occurred. App execution can not be continued."
let source = ErrorSource.internalInconsistency
}
HealableError
A healable (recoverable) & localized error type without the overhead of NSError – truly designed for Swift. Use this for any edge-cases you can heal (recover from) like network timeouts (healing via Retry), network unauthorized responses (healing via Logout) etc.
Requirements
HealableError extends BaseError and therefore has the same requirements. In addition to that, you need to add:
healingOptions: [HealingOption]
Provides an array of possible healing options to present to the user. A healing option consists of the following:
style: Style: The style of the healing option. One of: .normal, .recommended or .destructive
title: String: The title of the healing option.
handler: () -> Void: The code to be executed when the user chooses the healing option.
Note that you must provide at least one healing option.
Example
struct NetworkUnavailableError: HealableError {
private let retryClosure: () -> Void
init(retryClosure: @escaping () -> Void) {
self.retryClosure = retryClosure
}
let errorDescription = "Could not connect to server. Please check your internet connection and try again."
let source = ErrorSource.externalSystemUnavailable
var healingOptions: [HealingOption] {
let retryOption = HealingOption(style: .recommended, title: "Try Again", handler: retryClosure)
let cancelOption = HealingOption(style: .normal, title: "Cancel", handler: {})
return [retryOption, cancelOption]
}
}
Default Error Types
MungoHealer provides one basic implementation of each error protocol which you can use for convenience so you don’t have to write a new error type for simple message errors. These are:
MungoHealer makes handling errors easier by providing the ErrorHandler protocol and a default implementation of it based on alert views, namely AlertLogErrorHandler.
The easiest way to get started with MungoHealer is to use a global variable and set it in your AppDelegate.swift like this:
Note that the following steps were taken in the above code:
Add import MungoHealer at the top
Add var mungo: MungoHealer! global variable
Add a private configureMungoHealer() method
Provide your preferred logError handler (e.g. SwiftyBeaver)
Call configureMungoHealer() on app launch
As you can see, the AlertLogErrorHandler receives two parameters: The first is the window so it can find the current view controller to present alerts within. The second is a error log handler – the AlertLogErrorHandler not only presents alerts when there is a localized error, but it will also log all errors calling the logError handler with the errors localized description.
Custom ErrorHandler
While starting with the AlertLogErrorHandler is recommended by default, you might of course want to handle errors differently then just with system alerts & logs. For these cases, you just need to implement your own error handler by conforming to ErrorHandler which requires the following methods:
handle(error: Error): Called for “normal” error types.
handle(baseError: BaseError): Called for base error types.
handle(fatalError: FatalError): Called for fatal error types – App should crash at the end of this method.
handle(healableError: HealableError): Calles for healable error types.
See the implementation of AlertLogErrorHandlerhere for a working example.
Note that you don’t have to use a single global variable named mungo as in the example above. You could also write your own Singleton with multiple MungoHealer objects, each with a different ErrorHandler type. This way you could choose to either show an alert or your custom handling, depending on the context. The Singleton might look something like this:
enum ErrorHandling {
static var alertLogHandler: MungoHealer!
static var myCustomHandler: MungoHealer!
}
Usage Example
Once you’ve written your own error types and configured your error handler, you should write throwing methods and deal with errors either directly (as before) or use MungoHealer’s handle method which will automatically deal with the error cases.
We don’t need to deal with error handling on the call side which makes our code both more readable & more fun to write. Instead, we define how to deal with the errors at the point where the error is thrown/defined. On top of that, the way errors are communicated to the user is abstracted away and can be changed App-wide by simply editing the error handler code. This also makes it possible to handle errors in the model or networking layer without referencing any UIKit classes.
For cases where you just want one catch-all where you just call the handle(error) method, there’s even a shorthand which will deal with this automatically. Just use this instead of the above code:
So as you can see, used wisely, MungoHealer can help to make your code cleaner, less error prone and it can improve the User Experience for your users.
Installation • Usage • Issues • Contributing • License
MungoHealer
Error Handler based on localized & healable (recoverable) errors without the overhead of NSError (which you would have when using LocalizedError & RecoverableError instead).
Why use MungoHealer?
When developing a new feature for an App developers often need to both have presentable results fast and at the same time provide good user feedback for edge cases like failed network requests or invalid user input.
While there are many ways to deal with such situations, MungoHealer provides a straightforward and Swift-powered approach that uses system alerts for user feedback by default, but can be easily customized to use custom UI when needed.
tl;dr
Here’s a very simple example of basic error handling without MungoHealer:
Using MungoHealer the above code becomes this:
Installation
Installing via Carthage & CocoaPods are both supported.
Support for SPM is currently not possible as this framework uses UIKit.
Usage
Please also have a look at the
MungoHealer iOS-Demo
project in the subfolderDemos
for a live usage example.Features Overview
Defining Errors
MungoHealer is based on Swifts built-in error handling mechanism. So before we can throw any useful error message when something goes wrong, we need to define our errors.
MungoHealer can deal with any errors thrown by system frameworks or third party libraries alike, but to make use of the user feedback automatics, you need to implement one of MungoHealers error type protocols:
BaseError
A localized error type without the overhead of NSError – truly designed for Swift. Use this for any error you want to provide localized user feedback for.
Requirements
source: ErrorSource
A classification of the errors source. MungoHealer will automatically provide an alert title based on it. The available options are:.invalidUserInput
.internalInconsistency
.externalSystemUnavailable
.externalSystemBehavedUnexpectedlyBased
The options are explained in detail here.
errorDescription: String
A localized message describing what error occurred. This will be presented to the user as the alerts message by default when the error occurs.debugDescription: String?
An optional message describing the error in more technical detail for debugging purposes. This will not be presented to the user and so for only logged.Example
FatalError
A non-healable (non-recoverable) & localized fatal error type without the overhead of NSError – truly designed for Swift. Use this as an alternative for
fatalError
and tasks like force-unwrapping when you don’t expect anil
value and therefore don’t plan to heal (recover from).Note that throwing a
FatalError
will crash your app, just likefatalError()
or force-unwrappingnil
would. The difference is, that here the user is first presented with an error message which is a better user experience. Additionally, before the app crashes you have the chance to do any cleanup or reporting tasks via a callback if you need to.It is highly recommended to keep the suffix
FatalError
in your custom errors class name to clearly communicate that throwing this will crash the app.Requirements
FatalError
has the exact same requirements asBaseError
. In fact its type declaration is as simple as this:The only difference is semantics – the data provided by a
FatalError
will be used for alert title & message as well. But confirming the alert will crash the app.Example
HealableError
A healable (recoverable) & localized error type without the overhead of NSError – truly designed for Swift. Use this for any edge-cases you can heal (recover from) like network timeouts (healing via Retry), network unauthorized responses (healing via Logout) etc.
Requirements
HealableError
extendsBaseError
and therefore has the same requirements. In addition to that, you need to add:healingOptions: [HealingOption]
Provides an array of possible healing options to present to the user. A healing option consists of the following:style: Style
: The style of the healing option. One of:.normal
,.recommended
or.destructive
title: String
: The title of the healing option.handler: () -> Void
: The code to be executed when the user chooses the healing option.Note that you must provide at least one healing option.
Example
Default Error Types
MungoHealer provides one basic implementation of each error protocol which you can use for convenience so you don’t have to write a new error type for simple message errors. These are:
MungoError
BaseError
init
takessource: ErrorSource
&message: String
Example Usage:
MungoFatalError
FatalError
init
takessource: ErrorSource
&message: String
Example Usage:
MungoHealableError
HealableError
init
takessource: ErrorSource
&message: String
init
additionally takeshealOption: HealOption
Example Usage:
Error Handling
MungoHealer makes handling errors easier by providing the
ErrorHandler
protocol and a default implementation of it based on alert views, namelyAlertLogErrorHandler
.The easiest way to get started with MungoHealer is to use a global variable and set it in your AppDelegate.swift like this:
Note that the following steps were taken in the above code:
import MungoHealer
at the topvar mungo: MungoHealer!
global variableconfigureMungoHealer()
methodlogError
handler (e.g. SwiftyBeaver)configureMungoHealer()
on app launchAs you can see, the
AlertLogErrorHandler
receives two parameters: The first is thewindow
so it can find the current view controller to present alerts within. The second is a error log handler – theAlertLogErrorHandler
not only presents alerts when there is a localized error, but it will also log all errors calling thelogError
handler with the errors localized description.Custom
ErrorHandler
While starting with the
AlertLogErrorHandler
is recommended by default, you might of course want to handle errors differently then just with system alerts & logs. For these cases, you just need to implement your own error handler by conforming toErrorHandler
which requires the following methods:handle(error: Error)
: Called for “normal” error types.handle(baseError: BaseError)
: Called for base error types.handle(fatalError: FatalError)
: Called for fatal error types – App should crash at the end of this method.handle(healableError: HealableError)
: Calles for healable error types.See the implementation of
AlertLogErrorHandler
here for a working example.Note that you don’t have to use a single global variable named
mungo
as in the example above. You could also write your own Singleton with multipleMungoHealer
objects, each with a differentErrorHandler
type. This way you could choose to either show an alert or your custom handling, depending on the context. The Singleton might look something like this:Usage Example
Once you’ve written your own error types and configured your error handler, you should write throwing methods and deal with errors either directly (as before) or use MungoHealer’s
handle
method which will automatically deal with the error cases.Here’s a throwing method:
You can see that different kinds of errors could be thrown here. All of them can be handled at once as easy as this:
We don’t need to deal with error handling on the call side which makes our code both more readable & more fun to write. Instead, we define how to deal with the errors at the point where the error is thrown/defined. On top of that, the way errors are communicated to the user is abstracted away and can be changed App-wide by simply editing the error handler code. This also makes it possible to handle errors in the model or networking layer without referencing any
UIKit
classes.For cases where you just want one catch-all where you just call the
handle(error)
method, there’s even a shorthand which will deal with this automatically. Just use this instead of the above code:So as you can see, used wisely, MungoHealer can help to make your code cleaner, less error prone and it can improve the User Experience for your users.
Contributing
See the file CONTRIBUTING.md.
License
This library is released under the MIT License. See LICENSE for details.