Build Status Version: 0.3.2 Swift: 5.0 Platforms: iOS | tvOS License: MIT

安装使用问题贡献许可

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)
    }
)

安装

支持通过 CarthageCocoaPods 安装。

由于此框架使用 UIKit,因此目前不支持 SPM。

使用

另请查看子文件夹 Demos 中的 MungoHealer iOS-Demo 项目,以获取实时使用示例。


功能概述


定义错误

MungoHealer 基于 Swift 内置的 错误处理机制。因此,在发生错误时,在抛出任何有用的错误消息之前,我们需要定义我们的错误。

MungoHealer 可以处理系统框架或第三方库抛出的任何错误,但要利用用户反馈自动化,您需要实现 MungoHealer 的错误类型协议之一

BaseError

一种本地化的错误类型,没有 NSError 的开销 – 真正为 Swift 设计。对于您想要提供本地化用户反馈的任何错误,请使用此类型。

要求

source: ErrorSource 错误来源的分类。 MungoHealer 将自动基于此提供警报标题。可用的选项是

这些选项的详细说明请参见 此处

errorDescription: String 描述发生错误的本地化消息。 默认情况下,当发生错误时,此消息将作为警报消息呈现给用户。

debugDescription: 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] 提供可能呈现给用户的修复选项数组。 修复选项包括以下内容

请注意,您必须至少提供一个修复选项。

示例
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

使用示例

func fetchImage(urlPath: String) {
  guard let url = URL(string: urlPath) else {
    throw MungoError(source: .invalidUserInput, message: "Invalid Path")
  }

  // ...
}
MungoFatalError

使用示例

func fetchImage(urlPath: String) {
  guard let url = URL(string: urlPath) else {
    throw MungoFatalError(source: .invalidUserInput, message: "Invalid Path")
  }

  // ...
}
MungoHealableError

使用示例

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)
    }
}

请注意,上面的代码中执行了以下步骤

  1. 在顶部添加 import MungoHealer
  2. 添加 var mungo: MungoHealer! 全局变量
  3. 添加一个私有的 configureMungoHealer() 方法
  4. 提供您首选的 logError 处理程序(例如 SwiftyBeaver
  5. 在应用程序启动时调用 configureMungoHealer()

正如您所看到的,AlertLogErrorHandler 接收两个参数:第一个是 window,以便它可以找到当前的视图控制器以在其内呈现警报。 第二个是错误日志处理程序 – AlertLogErrorHandler 不仅在出现本地化错误时呈现警报,而且它还将记录所有错误,并使用错误的本地化描述调用 logError 处理程序。

自定义 ErrorHandler

虽然默认情况下建议从 AlertLogErrorHandler 开始,但您当然可能希望以不同于仅使用系统警报和日志的方式处理错误。 对于这些情况,您只需要通过遵循 ErrorHandler 来实现自己的错误处理程序,这需要以下方法

有关工作示例,请参见 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。