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

Edw*_*eer 9 reflection enums swift

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

请参阅Swift编译器开发人员的Twitter响应

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

原始问题:

我有一些用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 }
    }
Run Code Online (Sandbox Code Playgroud)

Chr*_*den 11

不幸的是,在这一点上看起来并不像Swift那样,但我已经考虑过你的问题了一段时间,我会提出三种方式,Swift团队可以帮助你解决这个问题.

  1. 修复枚举镜像.最直接的解决方案是我确定你已经尝试过的解决方案.您正在尝试构建一个反射库,并且您希望反映一个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
            }
        }
    }
    
    Run Code Online (Sandbox Code Playgroud)

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

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

    switch theEnum {
    case let rawEnum as protocol<RawRepresentable where RawValue == String>:
       return rawEnum.rawValue
    // And so on
    }
    
    Run Code Online (Sandbox Code Playgroud)

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

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

  1. 扩展协议以符合协议. 我真的以为我可以让这个解决方案适合你,但是没有.由于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)
        }
    }
    
    Run Code Online (Sandbox Code Playgroud)

大!现在我们所要做的就是扩展RawRepresentableReflectable我们可以首先进行投射theEnum as Reflectable(不需要相关的类型),然后调用reflect(theEnum)以给我们提供令人敬畏的自定义镜像:

    extension RawRepresentable: Reflectable {
        func getMirror() -> MirrorType {
            return RawRepresentableMirror(self)
        }
    }
Run Code Online (Sandbox Code Playgroud)

编译器错误:协议"RawRepresentable"的扩展不能具有继承子句

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

    extension MyClass: MyProtocol {
        // Conform to new protocol
    }
Run Code Online (Sandbox Code Playgroud)

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

    extension MyProtocol {
        // Default implementations for MyProtocol
    }
Run Code Online (Sandbox Code Playgroud)

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

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

protocol AnyRawRepresentable {
    var anyRawValue: Any { get }
}

extension RawRepresentable: AnyRawRepresentable {
    var anyRawValue: Any {
        return rawValue
    }
}
Run Code Online (Sandbox Code Playgroud)

以这种方式扩展协议本身并不会扩展继承.相反,它只是对编译器说"只要存在符合的类型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
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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

选项#2的适当术语("允许具有已定义关联类型的协议类型")是广义存在.这将允许具有关联类型的协议可能自动返回Any存在关联类型的位置或允许如下语法:

anyEnum as? Any<RawRepresentable where .RawValue == String>
Run Code Online (Sandbox Code Playgroud)

在邮件列表上偶尔会提到选项#3,这是一个经常被拒绝的请求功能,但这并不是说它不能包含在Swift的未来版本中.在Generics Manifesto中,它被称为"通过协议扩展的条件一致性".虽然认识到它的强大功能,但遗憾的是,有效实施"几乎是不可能的".

  • @DannieP很遗憾只使用Mirror,你仍然无法从枚举中获取rawValue.为了解决这个问题,我创建了一个RawEnum协议.如果你用它扩展你的枚举,那么就可以得到rawValue.请参阅此处的示例代码:https://github.com/evermeer/Stuff#enum我在EVReflection中使用相同的技术 (2认同)