swift – 从通用函数中的枚举中获取rawValue

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

Twitter response from Swift compiler developer

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

原始问题:

我有一些快速写的generic reflection code.在该代码中,我无法获取基于枚举的属性的值.问题归结为我无法对属性值为Any的属性值执行.rawValue. 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 }
    }
不幸的是,这看起来在Swift中看起来不太可能,但我已经考虑过你的问题了一段时间,我会提出三种方式,Swift团队可以帮助你解决这个问题.

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

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
        }
    }
}

但是,这不起作用.您会发现镜像的计数为0.我真的认为这是Swift团队在Swift._EnumMirror实现方面的疏忽,我将对此提出一个雷达. rawValue绝对是一个合法的财产.这是一个奇怪的场景,因为不允许枚举具有其他存储属性.此外,您的枚举声明永远不会明确地符合RawRepresentable,也不会声明rawValue属性.编译器只是推断当你输入enum MyEnum:String或:Int或者其他什么时.在我的测试中,看起来无论属性是在协议中定义还是关联类型的实例都无关紧要,它应该仍然是镜像中表示的属性.

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

switch theEnum {
case let rawEnum as protocol<RawRepresentable where RawValue == String>:
   return rawEnum.rawValue
// And so on
}

但这并没有在Swift中定义.如果您只是尝试转换为RawRepresentable,Swift编译器会告诉您,您只能在通用函数中使用它,但是当我查看您的代码时,这只会导致您陷入一个兔子洞.通用函数在编译时需要类型信息才能工作,而这正是您没有使用任何实例的东西.

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

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

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强制转换为Reflectable(不需要关联类型),然后调用reflect(theEnum)来为我们提供令人敬畏的自定义镜像:

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

Compiler error: Extension of protocol ‘RawRepresentable’ cannot have
an inheritance clause

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

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

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

extension MyProtocol {
        // Default implementations for MyProtocol
    }

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

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

protocol AnyRawRepresentable {
    var anyRawValue: Any { get }
}

extension RawRepresentable: AnyRawRepresentable {
    var anyRawValue: Any {
        return rawValue
    }
}

以这种方式扩展协议本身并不会扩展继承.相反,它只是对编译器说“只要存在符合RawRepresentable的类型,使该类型也符合使用此认实现的AnyRawRepresentable”. AnyRawRepresentable不具有关联的类型要求,但仍可以将rawValue检索为Any.在我们的代码中,然后:

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
    }
}

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

更新:从Swift 4开始,以上选项均不可用.我没有收到有关为什么RawRepresentable枚举上的Mirror不包含其rawValue的响应.对于选项#2和#3,它们仍然在未来Swift版本的可能范围内.它们已在Swift邮件列表和Swift团队的Doug Gregor撰写的Generics Manifesto文档中提及.

选项#2(“允许具有已定义关联类型的协议类型”)的正确术语是generalized existentials.这将允许具有关联类型的协议可能自动返回任何存在关联类型的地方或允许如下语法:

anyEnum as? Any<RawRepresentable where .RawValue == String>

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

相关文章

软件简介:蓝湖辅助工具,减少移动端开发中控件属性的复制和粘...
现实生活中,我们听到的声音都是时间连续的,我们称为这种信...
前言最近在B站上看到一个漂亮的仙女姐姐跳舞视频,循环看了亿...
【Android App】实战项目之仿抖音的短视频分享App(附源码和...
前言这一篇博客应该是我花时间最多的一次了,从2022年1月底至...
因为我既对接过session、cookie,也对接过JWT,今年因为工作...