基于本地化和可修复(可恢复)错误的错误处理程序,没有 NSError 的开销(使用 LocalizedError 和 RecoverableError 时会遇到)。
在为 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 的错误类型协议之一
一种本地化的错误类型,没有 NSError 的开销 – 真正为 Swift 设计。对于您想要提供本地化用户反馈的任何错误,请使用此类型。
source: ErrorSource
错误来源的分类。 MungoHealer 将自动基于此提供警报标题。可用的选项是
.invalidUserInput
.internalInconsistency
.externalSystemUnavailable
.externalSystemBehavedUnexpectedlyBased
这些选项的详细说明请参见 此处。
errorDescription: String
描述发生错误的本地化消息。 默认情况下,当发生错误时,此消息将作为警报消息呈现给用户。
debugDescription: String?
用于调试目的的、更技术性地描述错误的可选消息。 这将不会呈现给用户,因此仅记录。
struct PasswordValidationError: BaseError {
let errorDescription = "Your password confirmation didn't match your password. Please try again."
let source = ErrorSource.invalidUserInput
}
一种不可修复(不可恢复)和本地化的致命错误类型,没有 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
}
一种可修复(可恢复)和本地化的错误类型,没有 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 为每个错误协议提供了一个基本实现,您可以方便地使用它,因此您不必为简单的消息错误编写新的错误类型。 这些是
BaseError
init
接受 source: ErrorSource
和 message: String
使用示例
func fetchImage(urlPath: String) {
guard let url = URL(string: urlPath) else {
throw MungoError(source: .invalidUserInput, message: "Invalid Path")
}
// ...
}
FatalError
init
接受 source: ErrorSource
和 message: String
使用示例
func fetchImage(urlPath: String) {
guard let url = URL(string: urlPath) else {
throw MungoFatalError(source: .invalidUserInput, message: "Invalid Path")
}
// ...
}
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
处理程序。
虽然默认情况下建议从 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。