我想在Swift中做一些我习惯在其他多种语言中做的事情:用自定义消息抛出运行时异常。例如(在Java中):

throw new RuntimeException("A custom message here")

我知道我可以抛出符合ErrorType协议的枚举类型,但我不希望必须为抛出的每种类型的错误定义枚举。理想情况下,我希望能够尽可能地模拟上面的示例。我考虑创建一个实现ErrorType协议的自定义类,但我甚至不知道该协议需要什么。想法吗?


当前回答

@nick-keets的解决方案是最优雅的,但它确实打破了我的测试目标与以下编译时错误:

'String'与协议'Error'的冗余一致性

这是另一种方法:

struct RuntimeError: Error {
    let message: String

    init(_ message: String) {
        self.message = message
    }

    public var localizedDescription: String {
        return message
    }
}

并使用:

throw RuntimeError("Error message.")

其他回答

看看这个很酷的版本。其思想是同时实现String和ErrorType协议,并使用错误的rawValue。

enum UserValidationError: String, Error {
  case noFirstNameProvided = "Please insert your first name."
  case noLastNameProvided = "Please insert your last name."
  case noAgeProvided = "Please insert your age."
  case noEmailProvided = "Please insert your email."
}

用法:

do {
  try User.define(firstName,
                  lastName: lastName,
                  age: age,
                  email: email,
                  gender: gender,
                  location: location,
                  phone: phone)
}
catch let error as User.UserValidationError {
  print(error.rawValue)
  return
}

首先,让我们看看LocalizedErrorEnum的使用示例,然后看看如何使这些示例工作(在源代码部分)。

使用

do {
    let path = "/my/path/to/file.txt";
    throw MyErrorCategory.FileNotFound(
        atPath: path
    );
} catch {
    print(error.localizedDescription);
}

输出:

Failed to find file. {
  atPath: /my/path/to/file.txt
}

定义:

public enum MyErrorCategory: LocalizedErrorEnum {
    case FileNotFound(String = "Failed to find file.", atPath: String)
    case Connection(String = "Connection fail - double check internet access.")
}

第一个参数被视为消息(在LocalizedErrorEnum enum中)。

所需功能(背景)

#1首先,我想要没有复制/粘贴的消息,并且能够捕捉一组不同的错误情况,而不列出每个情况(解决方案,enum是非常独特的,没有复制/粘贴需要,每个enum可以被认为是另一组)。

其次,一些像“FileNotFound”这样的错误需要有变量上下文/细节,比如file-path(但Raw-Value enum不支持实例变量,与#1不同的是,内置enum不是解决方案)。

#3最后,我希望能够单独捕获每个情况,而不是捕获整个结构和/或类,然后在捕获内做切换,并希望避免忘记我们不处理的情况的重新抛出。

源代码(满足需求的解决方案)

只需从下面复制LocalizedErrorEnum并将其添加到您的项目中一次,并根据需要多次使用关联枚举。

public protocol LocalizedErrorEnum: LocalizedError {
    var errorDescription: String? { get }
}

extension LocalizedErrorEnum {
    public var errorDescription: String? {
        if let current = Mirror(reflecting: self).children.first {
            let mirror = Mirror(reflecting: current.value);
            // Initial error description.
            let message = mirror.children.first?.value as? String
                ?? current.label ?? "Unknown-case";
            var context = "";
            // Iterate additional context.
            var i = 0;
            for associated in mirror.children {
                if i >= 1 {
                    if let text = associated.value as? String {
                        context += "\n  ";
                        if let label: String = associated.label {
                            context += "\(label): "
                        }
                        context += text;
                    }
                }
                i += 1;
            }
            return context.isEmpty ? message : (
                message + " {" + context + "\n}"
            );
        }
        return "\(self)";
    }
}

请注意,正如我的个人资料中提到的,在Apache 2.0许可下使用上述代码也是允许的(不需要归属)。 如果您不需要附加上下文变量的错误(或与其他方法进行比较),请参阅我的其他答案。

最简单的方法可能是定义一个自定义enum,只有一个case,并附加一个String:

enum MyError: ErrorType {
    case runtimeError(String)
}

或者,在Swift 4中:

enum MyError: Error {
    case runtimeError(String)
}

示例用法如下:

func someFunction() throws {
    throw MyError.runtimeError("some message")
}
do {
    try someFunction()
} catch MyError.runtimeError(let errorMessage) {
    print(errorMessage)
}

如果您希望使用现有的错误类型,最常用的错误类型是NSError,您可以创建一个工厂方法来创建并抛出一个带有自定义消息的错误类型。

最简单的方法是使String符合Error:

extension String: Error {}

然后你可以抛出一个字符串:

throw "Some Error"

为了让字符串本身成为错误的localizedString,你可以扩展LocalizedError:

extension String: LocalizedError {
    public var errorDescription: String? { return self }
}

最简单的解决方案,没有额外的扩展,枚举,类等:

NSException(name:NSExceptionName(rawValue: "name"), reason:"reason", userInfo:nil).raise()