Error Handler

Travis

优雅且灵活的 Swift 错误处理

ErrorHandler 使您能够使用简洁流畅的 API,通过几行代码表达复杂的错误处理逻辑。

安装

CocoaPods

要使用 CocoaPods 将 ErrorHandler 集成到您的 Xcode 项目中,请在您的 Podfile 中添加以下条目:

target '<Your Target Name>' do
    pod 'ErrorHandler'
end

或者,如果您正在使用 Alamofire 并希望利用 ErrorHandler 提供的便利扩展来处理具有无效 http 状态的 Alamofire 错误

target '<Your Target Name>' do
    pod 'ErrorHandler'
    pod 'ErrorHandler/Alamofire'
end

然后,运行以下命令

$ pod install

Carthage

要使用 Carthage 将 ErrorHandler 集成到您的 Xcode 项目中,请在您的 Cartfile 中指定它:

github "Workable/swift-error-handler"

运行 carthage update 以构建框架,并将构建的 ErrorHandler.framework 拖入您的 Xcode 项目。

Swift Package Manager

要使用 Apple 的 Swift 包管理器进行集成,请将以下内容作为依赖项添加到您的 Package.swift 文件中:

import PackageDescription

let package = Package(
    name: "MyApp",
    dependencies: [
        .package(url: "https://github.com/Workable/swift-error-handler.git", from: "0.8")
    ]
)

用法

假设我们正在构建一个使用网络和本地数据库的 iOS 消息传递应用程序。

我们需要:

设置一次默认的 ErrorHandler

默认的 ErrorHandler 将包含在您的应用程序中通用的错误处理逻辑,并且您不希望重复。 您可以创建一个工厂来创建它,以便您可以从应用程序中的任何位置获取具有通用处理逻辑的新实例。

extension ErrorHandler {
    class var defaultHandler: ErrorHandler {

        return ErrorHandler()

            // Τhe error matches and the action is called if the matches closure returns true
            .on(matches: { (error) -> Bool in
                guard let error = error as? InvalidInputsError else { return false }
                // we will ignore errors with code == 5
                return error.code != 5
            }, do: { (error) in
                showErrorAlert("Invalid Inputs")
                return .continueMatching
            })

            // Variant using ErrorMatcher which is convenient if you want to
            // share the same matching logic elsewhere
            .on(InvalidStateMatcher(), do: { (_) in
                showErrorAlert("An error has occurred. Please restart the app.")
                return .continueMatching
            })

            // Handle all errors of the same type the same way
            .onError(ofType: ParsingError.self, do: { (error) in
                doSomething(with: error)
                return .continueMatching
            })

            // Handle a specific instance of an Equatable error type
            .on(DBError.migrationNeeded, do: { (_) in
                // Db.migrate()
                return .continueMatching
            })

            // You can tag matchers or matches functions in order to reuse them with a more memorable alias.
            // You can use the same tag for many matchers. This way you can group them and handle their errors together.
            .tag(NSErrorMatcher(domain: NSURLErrorDomain, code: NSURLErrorNetworkConnectionLost),
                with: "ConnectionError"
            )
            .tag(NSErrorMatcher(domain: NSURLErrorDomain, code: NSURLErrorNotConnectedToInternet),
                with: "ConnectionError"
            )
            .on(tag: "ConnectionError") { (_) in
                showErrorAlert("You are not connected to the Internet. Please check your connection and retry.")
                return .continueMatching
            }

            // You can use the Alamofire extensions to easily handle responses with invalid http status
            .onAFError(withStatus: 401, do: { (_) in
                showLoginScreen()
                return .continueMatching
            })
            .onAFError(withStatus: 404, do: { (_) in
                showErrorAlert("Resource not found!")
                return .continueMatching
            })

            // Handle unknown errors.
            .onNoMatch(do: { (_)  in
                showErrorAlert("An error occurred! Please try again. ")
                return .continueMatching
            })

            // Add actions - like logging - that you want to perform each time - whether the error was matched or not
            .always(do: { (error) in
                Logger.log(error)
                return .continueMatching
            })
    }
}

使用默认的处理程序来处理常见情况

通常,默认处理程序所知的案例就足够了。

do {
    try saveStatus()
} catch {
    ErrorHandler.defaultHandler.handle(error)
}

或者使用 tryWith 自由函数

tryWith(ErrorHandler.defaultHandler) {
    try saveStatus()
}

在需要时自定义错误处理程序。

在有额外上下文可用的情况下,您可以添加更多案例或覆盖已经提供的案例。

例如,在 SendMessageViewController 中:

sendMessage(message) { (response, error) in

            if let error = error {
                ErrorHandler.defaultHandler
                    .on(ValidationError.invalidEmail, do: { (_) in
                        updateEmailTextFieldForValidationError()
                        return .continueMatching
                    })
                    .onAFError(withStatus: 404, do: { (_) in
                        doSomethingSpecificFor404()
                        return .stopMatching
                    })
                    .onNoMatch(do: { (_) in
                        // In the context of the current screen we can provide a better message.
                        showErrorAlert("An error occurred! The message has not been sent.")
                        // We want to override the default onNoMatch handling so we stop searching for other matches.
                        return .stopMatching
                    })
                    .handle(error)
            }
        }

为什么?

在设计错误处理时,我们通常需要:

  1. 预期错误(例如网络、数据库错误等)拥有一个默认处理程序。
  2. 根据错误发生的上下文,以自定义方式处理特定错误 // 例如,上传文件时的网络错误,无效的登录信息。
  3. 未知错误(例如,我们没有自定义处理的错误)拥有一个兜底处理程序。
  4. 所有错误(已知和未知)执行一些操作,例如日志记录。
  5. 保持我们的代码 DRY (Don't Repeat Yourself).

Swift 有一个经过深思熟虑的错误处理模型,在便利性(自动传播)和清晰度-安全性(类型化传播标记传播)之间保持平衡。 因此,编译器可以提醒我们需要处理的错误,同时相对容易地传播错误并在堆栈中更高层级的位置处理它们。

然而,即使有了语言的帮助,在一个具有合理规模的应用程序中,以一种 临时 的方式实现上述目标也可能导致大量的 样板代码,这些代码编写起来和理解起来都非常 繁琐。 由于这种摩擦,开发人员经常选择吞掉错误或以相同的方式泛泛地处理所有错误。

该库通过提供一个抽象,用于使用一个有主见的流畅 API 定义灵活的错误处理规则,来解决这些问题。