我可以在 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)
}
}
}
假设您的枚举是 RawRepresentable
和 RawValue == String
...
Swift 中的枚举没有像 Enum
这样的特殊 "base class"。但在这种情况下,我们真的只需要利用它们的公共 属性 - RawRepresentable
和 Hashable
。当然,很多非枚举也有这个 属性。因此,我们的方法不仅适用于枚举,还适用于符合这两个协议的 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 帮不了你,所以你必须自己做扩展。
(Dictionary
和 RawRepresentable
来自 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)
我们正在使用 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)
}
}
}
假设您的枚举是 RawRepresentable
和 RawValue == String
...
Swift 中的枚举没有像 Enum
这样的特殊 "base class"。但在这种情况下,我们真的只需要利用它们的公共 属性 - RawRepresentable
和 Hashable
。当然,很多非枚举也有这个 属性。因此,我们的方法不仅适用于枚举,还适用于符合这两个协议的 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 帮不了你,所以你必须自己做扩展。
(Dictionary
和 RawRepresentable
来自 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)