更新8/28/2015:这将在Swift 2 中解决
查看Swift编译器开发人员的推特回应
2015年10月23日更新:使用Swift 2泛型,您仍然无法获得rawValue。您可以获得关联的值。
原始问题:
我有一些用swift编写的通用反射代码。在该代码中,我很难获得基于枚举的属性的值。问题归结为我无法对类型为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 .Aggregaten")
// 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团队可以帮助你解决这个问题的3种方法。
-
修复枚举的镜像最简单的解决方案我相信你已经尝试过了。您正在尝试构建一个反射库,并且希望反射一个
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 } } }
但是,这不起作用。您会发现镜像的count
为0
。我真的认为这是Swift团队在实施Swift._EnumMirror
时的疏忽,我将对此进行调查。rawValue
绝对是合法财产。这是一个奇怪的场景,因为枚举不允许有其他存储的属性。此外,枚举的声明从不显式符合RawRepresentable
,也不声明rawValue
属性。编译器只是在您键入enum MyEnum: String
或: Int
或其他类型时推断出。在我的测试中,似乎该属性是在协议中定义的还是关联类型的实例都无关紧要,它仍然应该是镜像中表示的属性。
-
允许具有已定义关联类型的协议类型正如我在上面的评论中提到的,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团队可能有充分的理由不想用协议来做这件事。协议的专用版本(即具有已知关联类型的协议引用)仍然不是具体的类型。我不会为这种能力而屏住呼吸。我认为这是我提出的解决方案中最薄弱的一个。
-
扩展协议以符合协议我真的以为我可以让这个解决方案为你工作,但遗憾的是,没有。由于Swift的enums内置镜像不能在
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
}
我确信我们可以扩展协议来实现其他协议,但显然不是!我看不出我们为什么不能让斯威夫特这么做。我认为这将非常符合2015年WWDC讨论的面向协议的编程范式。实现原始协议的具体类型将免费获得新的协议一致性,但具体类型也可以自由定义自己版本的新协议方法。我肯定会为此提出增强请求,因为我认为这可能是一个强大的功能。事实上,我认为它在你的反思库中可能非常有用。
编辑:回想我对答案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("允许具有定义的关联类型的协议类型")的正确术语是广义存在。这将允许具有相关类型的协议在存在相关类型的情况下自动返回Any
,或者允许如下语法:
anyEnum as? Any<RawRepresentable where .RawValue == String>
选项#3偶尔会在邮件列表中被提及,这是一个通常被拒绝的请求功能,但这并不是说它不能包含在Swift的未来版本中。在Generics Manifesto中,它被称为"通过协议扩展的条件一致性"。虽然承认的强大功能,但遗憾的是,它也指出,高效实施"几乎是不可能的">
好消息!
从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)")
}