我可以在 Swift 中在运行时从枚举名称和值的 rawValue 实例化或生成枚举值吗?

Can I instantiate or produce an enum value from the enum name and the value's rawValue at runtime in Swift?

我们正在使用 Swift 5.0。我需要定期将字符串列表转换为一组枚举案例。我轻松地编写了一个 Kotlin 函数,它在运行时接受一个枚举 class 和一个字符串列表,并将其转换为 Java EnumSet(好吧,2 个函数一起工作):

fun <EnumT : Enum<EnumT>> ConvertStrToEnum(enumClass: Class<EnumT>, str: String?): EnumT? {
    if (str == null)
        return null
    for (enumval in enumClass.enumConstants) {
        if (enumval.toString() == str)
            return enumval
    }
    throw IllegalArgumentException("Gave an invalid enum value for class ${enumClass.canonicalName}: [$str]")
}

fun <EnumT : Enum<EnumT> > ConvertStrArrayToEnumSet(enumClass: Class<EnumT>, array: List<String>?) : EnumSet<EnumT> {
    val set = EnumSet.noneOf(enumClass)
    array?.forEach { value -> ignoreExceptions { set.add(ConvertStrToEnum(enumClass, value)) } }
    return set
}

而且,要清楚,实际用法是:

var intent: EnumSet<Intent>
intent = ConvertStrArrayToEnumSet(Intent::class.java, filters.array(MatchFilter.Intent.jsonName))

我可以在 Swift5 中编写一个函数来实现相同的结果吗?我写这个是为了一次转换,这里是例子。如果我不能编写此函数,我将在整个应用程序中重复此样板代码。

  public var intents: Set<Intent>
  if let jsonIntents = filters?["intent"] as? Array<String> {
     for jsonIntent in jsonIntents {
        if let intent = Intent(rawValue: jsonIntent) {
           intents.insert(intent)
        }
     }
  }

假设您的枚举是 RawRepresentableRawValue == String...

Swift 中的枚举没有像 Enum 这样的特殊 "base class"。但在这种情况下,我们真的只需要利用它们的公共 属性 - RawRepresentableHashable。当然,很多非枚举也有这个 属性。因此,我们的方法不仅适用于枚举,还适用于符合这两个协议的 any 类型。很好,不是吗?

func convertStringArrayToEnumSet<T>(type: T.Type, _ strings: [String]) -> Set<T> 
    where T : RawRepresentable & Hashable, T.RawValue == String {
    Set(strings.compactMap(T.init(rawValue:)))
}

注意 compactMap 的使用,它会丢弃任何无效的原始值。

事实上,您不仅可以将其推广到字符串数组,还可以推广到任何数组:

func convertRawValueArrayToEnumSet<T>(type: T.Type, _ rawValues: [T.RawValue]) -> Set<T> 
    where T : RawRepresentable & Hashable {
    Set(rawValues.compactMap(T.init(rawValue:)))
}

Sweeper 的回答很好,但我看到您在错误处理上付出了一些努力。 Swift 帮不了你,所以你必须自己做扩展。

DictionaryRawRepresentable 来自 Swift 1,没有错误。他们从未现代化,只有 return 可选。)

/// Acts as a dictionary that `throw`s instead of returning optionals.
public protocol valueForKeyThrowingAccessor {
  associatedtype Key

  /// Should just be a throwing subscript, but those don't exist yet.
  func value<Value>(for: Key) throws -> Value
}
/// Acts as a dictionary.
public protocol valueForKeySubscript: valueForKeyThrowingAccessor {
  associatedtype Value

  subscript(key: Key) -> Value? { get }
}

public extension valueForKeySubscript {
  /// - Throws: `KeyValuePairs<Key, Value>.AccessError.noValue`
  func value(for key: Key) throws -> Value {
    guard let value = self[key]
    else { throw KeyValuePairs<Key, Value>.AccessError.noValue(key: key) }

    return value
  }

  /// - Throws: `KeyValuePairs<Key, Value>.AccessError.typeCastFailure`
  func value<Value>(for key: Key) throws -> Value {
    guard let value = try value(for: key) as? Value
    else { throw KeyValuePairs<Key, Value>.AccessError.typeCastFailure(key: key) }

    return value
  }
}

extension Dictionary: valueForKeySubscript { }
public extension KeyValuePairs {
  /// An error throw from trying to access a value for a key.
  enum AccessError: Error {
    case noValue(key: Key)
    case typeCastFailure(key: Key)
  }
}
public extension RawRepresentable {
  /// Like `init(rawValue:)`, if it was throwing instead of failable.
  /// - Throws: `RawRepresentableExtensions<Self>.Error.invalidRawValue`
  /// if there is no value of the type that corresponds with the specified raw value.
  init(_ rawValue: RawValue) throws {
    guard let instance = Self(rawValue: rawValue)
    else { throw RawRepresentableExtensions<Self>.Error.invalidRawValue(rawValue) }

    self = instance
  }
}

/// A namespace for nested types within `RawRepresentable`.
public enum RawRepresentableExtensions<RawRepresentable: Swift.RawRepresentable> {
  public enum Error: Swift.Error {
    case invalidRawValue(RawRepresentable.RawValue)
  }
}

public extension InitializableWithElementSequence where Element: RawRepresentable {
  /// - Throws: `RawRepresentableExtensions<Element>.Error.invalidRawValue`
  init<RawValues: Sequence>(rawValues: RawValues) throws
  where RawValues.Element == Element.RawValue {
    self.init(
      try rawValues.map(Element.init)
    )
  }
}
/// A type that can be initialized with a `Sequence` of its `Element`s.
public protocol InitializableWithElementSequence: Sequence {
  init<Sequence: Swift.Sequence>(_: Sequence)
  where Sequence.Element == Element
}

extension Array: InitializableWithElementSequence { }
extension Set: InitializableWithElementSequence { }
extension InitializableWithElementSequence where Element == Intent {
  init(filters: [String: Any]) throws {
    try self.init(
      rawValues: try filters.value(for: "intent") as [String]
    )
  }
}
try filters.map(Set.init)