swift 从泛型函数中的枚举获取rawValue

yacmzcpb  于 2023-03-17  发布在  Swift
关注(0)|答案(2)|浏览(180)

**2015年8月28日更新:**将在Swift 2中解决此问题

参见Twitter response from Swift compiler developer

**更新10/23/2015:**使用Swift 2泛型仍然无法获取rawValue。您可以获取关联的值。
原问题:

我用swift写了一些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 }
    }
kmynzznz

kmynzznz1#

不幸的是,目前看来这在Swift中是不可能的,但我已经考虑了您的问题一段时间,我将提出Swift团队可以帮助您解决此问题的3种方法。
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
        }
    }
}

然而,这是行不通的。你会发现镜像有一个0count。我真的认为这是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
}

但是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)来给予我们的自定义镜像:

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

编译器错误:协议“RawRepresentable”的扩展不能有继承子句
我们可以扩展具体的类型来实现新的协议,例如:

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。这将允许具有关联类型的协议可能自动地返回Any,其中存在关联类型或允许如下语法:

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

选项#3偶尔会在邮件列表中被提及,这是一个经常被拒绝的请求特性,但这并不是说它不能被包含在Swift的未来版本中。在Generics Manifesto中,它被称为“通过协议扩展的条件一致性”。虽然承认它作为一个特性的强大,但它也遗憾地指出,有效的实现是“几乎不可能的”。

guicsvcw

guicsvcw2#

好消息!
从Swift 5.6开始,您现在可以使用any关键字来执行此操作。
它允许强制转换为具有关联类型的协议,如下所示:

enum Option: String {
    case a = "OptionA", b = "OptionB"
}

let theEnum: Any = Option.a

if let theRawEnum = theEnum as? (any RawRepresentable) {
    print("RawValue: \(theRawEnum.rawValue)")
}

相关问题