MungoHealer
基于本地化和可修复(可恢复)错误的错误处理器,不包括 NSError(使用 LocalizedError 和 RecoverableError 时的开销)。
为什么使用 MungoHealer?
当为 App 开发新功能时,开发者通常需要快速展示可展示的结果,并在同时为边缘情况(如失败的请求或不正确的用户输入)提供良好的用户反馈。
虽然有很多处理这些情况的方法,但 MungoHealer 提供了一种简单直接的 Swift 方法,默认使用系统警报为用户提供反馈,但在需要时可以轻松定制为使用自定义 UI。
简介
以下是一个没有 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)
)
使用 MungoHealer,上述代码变为如下
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)
}
)
安装
由于此框架使用 UIKit,因此目前不支持 SPM。
使用方法
请查阅子文件夹 Demos
中的 MungoHealer iOS-Demo
项目,以便了解实时使用示例。
功能概述
定义错误
MungoHealer 基于Swift内置的错误处理机制。因此,在我们能够抛出任何有意义的错误消息之前,我们需要定义我们的错误。
MungoHealer 可以处理系统框架或第三方库抛出的任何错误,但为了使用用户反馈自动化,您需要实现MungoHealer的错误类型协议之一。
BaseError
一个没有NSError开销的本地化错误类型 - 真正为Swift而设计。您可以为任何希望提供本地化用户反馈的错误使用它。
要求
source: ErrorSource
错误来源的分类。MungoHealer将自动根据它提供警报标题。可用的选项有
invalidUserInput
internalInconsistency
externalSystemUnavailable
externalSystemBehavedUnexpectedly
这些选项的详细解释请参见此处。
errorDescription: String
描述发生的错误本地化消息。当错误发生时,这将是默认情况下呈现给用户警报消息。
示例
struct PasswordValidationError: BaseError {
let errorDescription = "Your password confirmation didn't match your password. Please try again."
let source = ErrorSource.invalidUserInput
}
FatalError
一种非可修复(不可恢复)且局部化的致命错误类型,不包含NSError的开销——真正为Swift设计的。当您不期待一个nil
值,因此不打算进行修复(恢复)时,可以将此作为fatalError
和强制展开等任务的替代方案。
请注意,抛出一个FatalError
将导致您的应用程序崩溃,就像抛出fatalError()
或强制展开nil
一样。不同的是,在此处,用户首先会看到一个错误消息,这将带来更好的用户体验。此外,在应用程序崩溃之前,如果您需要进行任何清理或报告任务,您有通过回调执行这些任务的机会。
强烈建议您在自定义错误类名称中保留后缀 FatalError,以便清楚地传达抛出此类错误会导致应用程序崩溃。
要求
FatalError
与BaseError
具有完全相同的要求。事实上,其类型声明就像这样
public protocol FatalError: BaseError {}
唯一的区别是语义——FatalError
提供的数据将用作弹窗标题和消息。但确认弹窗将导致应用程序崩溃。
示例
struct UnexpectedNilFatalError: FatalError {
let errorDescription = "An unexpected data inconsistency has occurred. App execution can not be continued."
let source = ErrorSource.internalInconsistency
}
HealableError
一种可修复(可恢复)且局部化的错误类型,不包含NSError的开销——真正为Swift设计的。您可以使用此来处理各种可以修复(恢复)的边缘情况,如网络超时(通过重试修复)、网络未授权响应(通过注销修复)等。
要求
HealableError
扩展了BaseError
,因此有相同的要求。除此之外,您还需要添加
healingOptions: [HealingOption]
提供一个数组,其中包含可供用户选择的可修复选项。一个修复选项由以下内容组成
style: Style
:修复选项的样式。以下之一:.normal
、.recommended
或.destructive
title: String
:修复选项的标题。handler: () -> Void
:当用户选择修复选项时要执行的代码。
请注意,您必须至少提供一个修复选项。
示例
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]
}
}
默认错误类型
MungoHealer为每个错误协议提供一个基本实现,您可以使用这些实现以便于使用,无需为简单的消息错误编写新错误类型。这些包括
MungoError
- 实现了
BaseError
init
接受source: ErrorSource
和message: String
示例用法
func fetchImage(urlPath: String) {
guard let url = URL(string: urlPath) else {
throw MungoError(source: .invalidUserInput, message: "Invalid Path")
}
// ...
}
MungoFatalError
- 实现了
FatalError
init
接受source: ErrorSource
和message: String
示例用法
func fetchImage(urlPath: String) {
guard let url = URL(string: urlPath) else {
throw MungoFatalError(source: .invalidUserInput, message: "Invalid Path")
}
// ...
}
MungoHealableError
- 实现了
HealableError
init
接受source: ErrorSource
和message: String
init
另外还接受healOption: HealOption
示例用法
func fetchImage(urlPath: String) {
guard let url = URL(string: urlPath) else {
let healingOption = HealingOption(style: .recommended, title: "Retry") { [weak self] in self?.fetchImage(urlPath: urlPath) }
throw MungoHealableError(source: .invalidUserInput, message: "Invalid Path", healingOption: healingOption)
}
// ...
}
错误处理
MungoHealer通过提供ErrorHandler
协议及其基于警告视图的默认实现AlertLogErrorHandler
,简化了错误处理过程。
开始使用MungoHealer的最简单方法是使用全局变量,并在您的AppDelegate.swift中设置它,如下所示
import MungoHealer
import UIKit
var mungo: MungoHealer!
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
configureMungoHealer()
return true
}
private func configureMungoHealer() {
let errorHandler = AlertLogErrorHandler(window: window!, logError: { print("Error: \($0)") })
mungo = MungoHealer(errorHandler: errorHandler)
}
}
请注意,以下步骤在上面的代码中已经执行
- 在顶部添加
import MungoHealer
- 添加全局变量
var mungo: MungoHealer!
- 添加一个私有的
configureMungoHealer()
方法 - 提供您的首选
logError
处理程序(例如,SwiftyBeaver) - 在应用启动时调用
configureMungoHealer()
如你所见,AlertLogErrorHandler
接收两个参数:第一个是window
,这样它可以找到当前视图控制器以呈现警告。第二个是错误日志处理程序——当存在本地化错误时,AlertLogErrorHandler
不仅会呈现警告,还会通过调用带有错误本地化描述的logError
处理程序来记录所有错误。
ErrorHandler
自定义虽然默认推荐从AlertLogErrorHandler
开始,但你可能希望以不同的方式处理错误,而不仅仅是使用系统警告和日志。在这种情况下,你需要通过自定义一个实现ErrorHandler
的协议的错误处理程序来处理错误,它需要以下方法:
handle(error: Error)
:对于“常规”错误类型调用。handle(baseError: BaseError)
:对于基本错误类型调用。handle(fatalError: FatalError)
:对于致命错误类型调用——应用应当在方法的末尾崩溃。handle(healableError: HealableError)
:对于可治疗的错误类型调用。
有关一个工作示例的AlertLogErrorHandler
实现,请参阅此处。
请注意,您不需要使用如上例所示的名为mungo
的单个全局变量。您还可以编写自己的Singleton,包含多个MungoHealer
对象,每个对象都有一个不同的ErrorHandler
类型。这样,您可以根据上下文选择显示警告或自定义处理。Singleton可能看起来像这样:
enum ErrorHandling {
static var alertLogHandler: MungoHealer!
static var myCustomHandler: MungoHealer!
}
使用示例
一旦你定义了自己的错误类型并配置了错误处理器,你应该编写抛出方法,并直接处理错误(就像之前一样),或者使用MungoHealer的handle
方法来自动处理错误情况。
以下是一个抛出方法的示例
private func fetchImage(urlPath: String) throws -> UIImage {
guard let url = URL(string: urlPath) else {
throw StringNotAValidURLFatalError()
}
guard let data = try? Data(contentsOf: url) else {
throw NetworkUnavailableError(retryClosure: { [weak self] intry self?.loadAvatarImage() })
}
guard let image = UIImage(data: data) else {
throw InvalidDataError()
}
return image
}
你可以看到这里可以抛出不同类型的错误。它们都可以像这样一次性处理。
private func loadAvatarImage() {
do {
imageView.image = try fetchImage(urlPath: user.avatarUrlPath)
} catch {
mungo.handle(error)
}
}
我们不需要在调用方处理错误,这使得我们的代码更易读,编写起来更有趣。相反,我们在错误抛出或定义的位置定义如何处理错误。除此之外,错误与用户之间的通信方式被抽象出来,可以通过简单地编辑错误处理器代码在应用范围内进行更改。这也使得在模型层或网络层处理错误成为可能,而不需要引用任何UIKit
类。
在只想调用handle(error)
方法的场景中,甚至有一个快捷方式可以自动处理这个操作。只需使用以下代码代替上面的代码即可
private func loadAvatarImage() {
mungo.do {
imageView.image = try fetchImage(urlPath: user.avatarUrlPath)
}
}
所以,就像你所看到的,如果你明智地使用MungoHealer,它可以帮助你的代码更干净、错误率更低,并且可以提高用户体验。
贡献
请参阅文件CONTRIBUTING.md。
许可证
本库遵循MIT许可证发布。详情请参阅LICENSE文件。