从泛型函数中获取enum中的rawValue

时间:2015-07-24 10:02:00

标签: swift reflection enums

2015年8月28日更新: 这将在Swift 2中解决

请参阅Twitter response from Swift compiler developer

2015年10月23日更新: 使用Swift 2泛型,你仍然无法获得rawValue。你可以得到相关的值。

原始问题:

我有一些generic reflection code用swift写的。在该代码中,我无法获取基于枚举的属性的值。问题归结为我无法对.rawValue类型的属性值执行Any这一事实。 Swift反射代码将返回枚举值为Any的类型。那么我怎样才能从Any到AnyObject,它是枚举的rawValue。

到目前为止,我发现的唯一解决方法是使用协议扩展所有枚举。您可以在下面看到使用此变通方法的单元测试。

如果不在原始枚举中添加代码,有没有办法解决这个问题?

对于我的反射代码,我需要getRawValue方法签名保持不变。

class WorkaroundsTests: XCTestCase {
    func testEnumToRaw() {
        let test1 = getRawValue(MyEnumOne.OK)
        XCTAssertTrue(test1 == "OK", "Could nog get the rawvalue using a generic function")
        let test2 = getRawValue(MyEnumTwo.OK)
        XCTAssertTrue(test2 == "1", "Could nog get the rawvalue using a generic function")
        let test3 = getRawValue(MyEnumThree.OK)
        XCTAssertTrue(test3 == "1", "Could nog get the rawvalue using a generic function")
    }


    enum MyEnumOne: String, EVRawString {
        case NotOK = "NotOK"
        case OK = "OK"
    }

    enum MyEnumTwo: Int, EVRawInt {
        case NotOK = 0
        case OK = 1
    }

    enum MyEnumThree: Int64, EVRaw {
        case NotOK = 0
        case OK = 1
        var anyRawValue: AnyObject { get { return String(self.rawValue) }}
    }

    func getRawValue(theEnum: Any) -> String {
        // What can we get using reflection:
        let mirror = reflect(theEnum)
        if mirror.disposition == .Aggregate {
            print("Disposition is .Aggregate\n")

            // OK, and now?

            // Thees do not complile:
            //return enumRawValue(rawValue: theEnum)
            //return enumRawValue2(theEnum )

            if let value = theEnum as? EVRawString {
                return value.rawValue
            }
            if let value = theEnum as? EVRawInt {
                return String(value.rawValue)
            }
        }
        var valueType:Any.Type = mirror.valueType
        print("valueType = \(valueType)\n")
        // No help from these:
        //var value = mirror.value  --> is just theEnum itself
        //var objectIdentifier = mirror.objectIdentifier   --> nil
        //var count = mirror.count   --> 0
        //var summary:String = mirror.summary     --> "(Enum Value)"
        //var quickLookObject = mirror.quickLookObject --> nil

        let toString:String = "\(theEnum)"
        print("\(toString)\n")
        return toString
    }

    func enumRawValue<E: RawRepresentable>(rawValue: E.RawValue) -> String {
        let value = E(rawValue: rawValue)?.rawValue
        return "\(value)"
    }

    func enumRawValue2<T:RawRepresentable>(rawValue: T) -> String {
        return "\(rawValue.rawValue)"
    }

}

    public protocol EVRawInt {
        var rawValue: Int { get }
    }
    public protocol EVRawString {
        var rawValue: String { get }
    }
    public protocol EVRaw {
        var anyRawValue: AnyObject { get }
    }

1 个答案:

答案 0 :(得分:11)

不幸的是,目前这在Swift中看起来并不可能,但我已经考虑过你的问题一段时间了,我会提出3种方法。 Swift团队可以帮助您解决这个问题。

  1. 为枚举修复镜像。最直接的解决方案是我确定您已经尝试过的解决方案。您正在尝试构建一个反射库,并且您希望反映一个Any值以查看它是否为枚举,如果是,则您想要看它是否有原始值。 {/ 1}}属性可通过以下代码访问:

    rawValue
  2. 然而,这不起作用。您会发现镜像的let mirror = reflect(theEnum) // theEnum is of Any type for i in 0..<mirror.count { if mirror[i].0 == "rawValue" { switch mirror[i].1.value { case let s as String: return s case let csc as CustomStringConvertible: return csc.description default: return nil } } } count。我真的认为这是Swift团队在0实施过程中的一个疏忽,我将对此提出一个雷达。 Swift._EnumMirror绝对是合法的财产。这是一个奇怪的场景,因为枚举不允许有其他存储的属性。此外,您的枚举声明从未明确符合rawValue,也未声明RawRepresentable属性。编译器只是在您键入rawValueenum MyEnum: String或其他内容时推断出。在我的测试中,似乎无论属性是在协议中定义还是关联类型的实例都不重要,它应该仍然是镜像中表示的属性。

    1. 允许具有已定义关联类型的协议类型。正如我在上面的评论中所提到的,Swift中的限制是您无法转换为具有关联类型的协议类型要求。您不能简单地转换为: Int,因为Swift不知道RawRepresentable属性将返回什么类型。可以想到rawValue之类的语法,或者RawRepresentable<where RawValue == String>。如果这是可能的,您可以尝试通过switch语句转换为类型,如:

      protocol<RawRepresentable where RawValue == String>
    2. 但是没有在Swift中定义。如果您只是尝试转换为switch theEnum { case let rawEnum as protocol<RawRepresentable where RawValue == String>: return rawEnum.rawValue // And so on } ,Swift编译器会告诉您,您只能在通用函数中使用它,但是当我查看您的代码时,这只会导致您失去一只兔子 - 洞。通用函数需要在编译时输入类型信息才能工作,这正是您不能使用RawRepresentable个实例的。

      Swift团队可以将协议更改为更像通用类和结构。例如,AnyMyGenericStruct<MyType>是合法专用的具体类型,您可以对其进行运行时检查和转换。但是,Swift团队可能有充分的理由不想用协议来做这件事。协议的专用版本(即具有已知相关类型的协议引用)仍然不是具体类型。我不会为这种能力屏住呼吸。我认为这是我提出的解决方案中最薄弱的一个。

      1. 扩展协议以符合协议。我真的认为我可以让这个解决方案适合你,但是没有。由于Swift用于枚举的内置镜像无法在MyGenericClass<MyType>上传递,我想为什么不实现我自己的通用镜像:

        rawValue
      2. 大!现在我们所要做的就是将struct RawRepresentableMirror<T: RawRepresentable>: MirrorType { private let realValue: T init(_ value: T) { realValue = value } var value: Any { return realValue } var valueType: Any.Type { return T.self } var objectIdentifier: ObjectIdentifier? { return nil } var disposition: MirrorDisposition { return .Enum } var count: Int { return 1 } subscript(index: Int) -> (String, MirrorType) { switch index { case 0: return ("rawValue", reflect(realValue.rawValue)) default: fatalError("Index out of range") } } var summary: String { return "Raw Representable Enum: \(realValue)" } var quickLookObject: QuickLookObject? { return QuickLookObject.Text(summary) } } 扩展为RawRepresentable,这样我们就可以先展开Reflectable(不需要相关的类型),然后调用theEnum as Reflectable给我们带来真棒自定义镜像:

        reflect(theEnum)
          

        编译器错误:协议扩展&#39; RawRepresentable&#39;不能有   继承条款

        Whaaaat?我们可以扩展具体类型来实现新协议,例如:

            extension RawRepresentable: Reflectable {
                func getMirror() -> MirrorType {
                    return RawRepresentableMirror(self)
                }
            }
        

        从Swift 2开始,我们可以扩展协议以提供函数的具体实现,例如:

            extension MyClass: MyProtocol {
                // Conform to new protocol
            }
        

        我确信我们可以扩展协议来实现其他协议,但显然不是!我认为没有理由不让Swift这样做。我认为这非常适合面向协议的编程范例,这是WWDC 2015的讨论。实现原始协议的具体类型将免费获得新的协议一致性,但具体类型也可以自由定义新协议方法的版本。我绝对会提出增强请求,因为我认为它可能是一个强大的功能。事实上,我认为它在您的反射库中非常有用。

        编辑:回想我对答案2的不满,我认为在广泛使用这些协议方面有更优雅和现实的可能性,这实际上结合了我对答案3的看法扩展协议以符合新协议。这个想法是让具有相关类型的协议符合检索原始类型擦除属性的新协议。这是一个例子:

            extension MyProtocol {
                // Default implementations for MyProtocol
            }
        

        以这种方式扩展协议本身并不会扩展继承。相反,它只是对编译器说'#34;只要存在符合protocol AnyRawRepresentable { var anyRawValue: Any { get } } extension RawRepresentable: AnyRawRepresentable { var anyRawValue: Any { return rawValue } } 的类型,使该类型也符合RawRepresentable的默认实现。&#34; AnyRawRepresentable不会有关联的类型要求,但仍可以AnyRawRepresentable检索rawValue。在我们的代码中,然后:

        Any

        这种解决方案可以广泛用于任何类型的协议类型的协议。我同样会在我的提案中将这个想法包含在Swift团队中,以扩展协议一致性协议。

        更新:从Swift 4开始,以上选项都不可用。我没有收到有关if let anyRawEnum = theEnum as? AnyRawRepresentable { // Able to cast to let anyRawValue = anyRawEnum.anyRawValue // anyRawValue is of type Any switch anyRawValue { case let s as String: return s case let csc as CustomStringConvertible: return csc.description default: return nil } } 枚举上的Mirror为何不包含的回复它的RawRepresentable。对于选项#2和#3,它们仍然在未来Swift版本的可能范围内。它们已在Swift邮件列表和Swift团队的Doug Gregor撰写的Generics Manifesto文档中提及。

        选项#2的正确术语(&#34;允许具有已定义关联类型的协议类型&#34;)为generalized existentials。这将允许具有关联类型的协议可能自动返回rawValue,其中存在关联类型或允许语法如下:

        Any

        邮件列表上偶尔会提到选项#3,这是一个经常被拒绝的请求功能,但这并不是说它不能包含在未来的Swift版本中。在Generics Manifesto中,它被称为"Conditional conformances via protocol extensions"。虽然认识到它的强大功能,但遗憾的是,它表明有效的实施几乎是不可能的。&#34;