MungoHealer 0.3.0

MungoHealer 0.3.0

Cihat Gündüz维护。



Build Status Version: 0.3.0 Swift: 4.2 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将自动根据它提供警报标题。可用的选项有

  • 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,以便清楚地传达抛出此类错误会导致应用程序崩溃。

要求

FatalErrorBaseError具有完全相同的要求。事实上,其类型声明就像这样

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: ErrorSourcemessage: String

示例用法

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

  // ...
}
MungoFatalError
  • 实现了FatalError
  • init接受source: ErrorSourcemessage: String

示例用法

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

  // ...
}
MungoHealableError
  • 实现了HealableError
  • init接受source: ErrorSourcemessage: 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)
    }
}

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

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