在通用函数中从枚举中获取 rawValue

Get rawValue from enum in a generic function

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

中解决

Twitter response from Swift compiler developer

2015 年 10 月 23 日更新: 使用 Swift 2 个泛型,您仍然无法获得 rawValue。你这样做可以获得关联值。

原题:

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

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

有没有办法在不向原始枚举添加代码的情况下解决这个问题?

对于我的反射代码,我需要 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 中看起来不太可能,但我已经考虑了您的问题一段时间,我将提出 3 种方法 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
            }
        }
    }
    

但是,这不起作用。你会发现镜子的 count0。我真的认为这是 Swift 团队在实施 Swift._EnumMirror 时的疏忽,我将就此提交雷达。 rawValue绝对是合法的属性。这是一个奇怪的场景,因为不允许枚举具有其他存储属性。此外,您的枚举声明从未明确符合 RawRepresentable,也未声明 [​​=20=] 属性。编译器只是在您键入 enum MyEnum: String: Int 或其他任何内容时推断。在我的测试中,似乎 属性 是在协议中定义还是关联类型的实例并不重要,它仍然应该是镜像中表示的 属性。

  1. 允许具有已定义关联类型的协议类型。 正如我在上面的评论中提到的,这是 Swift 中的一个限制,您不能强制转换到具有关联类型要求的协议类型。您不能简单地转换为 RawRepresentable,因为 Swift 不知道 rawValue 属性 将 return 是什么类型。 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
    }
    

但这在 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)
        }
    }
    

太棒了!现在我们所要做的就是将 RawRepresentable 扩展为 Reflectable 这样我们就可以首先转换 theEnum as Reflectable (不需要关联类型)然后调用 reflect(theEnum) 给我们我们的 awesome自定义镜像:

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

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

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

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

从Swift2开始,我们可以扩展协议来给出功能的具体实现,例如:

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

以这种方式扩展协议本身并不是扩展继承。相反,它只是告诉编译器 "Wherever there is a type that conforms to RawRepresentable, make that type also conform to AnyRawRepresentable with this default implementation." 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 团队提出的扩展协议与协议一致性的提案中。

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

选项 #2 ("Allow for protocol types with defined associated types") 的正确术语是 generalized existentials。这将允许具有关联类型的协议可能自动 return Any 存在关联类型或允许如下语法:

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

选项 #3 偶尔会在邮件列表中提到,这是一个通常被拒绝的请求功能,但这并不是说它不能包含在 Swift 的未来版本中。在泛型宣言中,它被称为 "Conditional conformances via protocol extensions"。虽然认识到它作为一项功能的强大功能,但令人遗憾的是,它还声明有效实施是 "nearly impossible."