Update 8/28/2015:
This will be solved in Swift 2
See Twitter response from Swift compiler developer
Update 10/23/2015:
With Swift 2 generics you still cannot get the rawValue. You do can get the associated value.
Original question:
I have some generic reflection code written in swift. In that code I'm having trouble getting the value for properties that are based on an enum. The problem comes down to the fact that I'm not able to execute the .rawValue
on the property value which is of type Any
. The Swift reflection code will return the enum's value as type Any
. So how can I get from an Any to an AnyObject which is the rawValue of the enum.
The only workaround i found so far is extending all enum's with a protocol. Below you can see a unit test that's OK using this workaround.
Is there any way to solve this without adding code to the original enum's?
for my reflection code I need the getRawValue
method signature to stay as it is.
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 }
}
Unfortunately, this doesn't look like it's possible in Swift at this point, but I've thought about your problem for a while, and I'll propose 3 ways that the Swift team could enable you to solve this problem.
Fix the mirror for enums. The most straightforward solution is one that I'm sure you already tried. You're trying to build a reflection library, and you'd like to reflect an Any
value to see if it's an enum, and if it is, you'd like to see if it has a raw value. The rawValue
property should be accessible via this code:
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
}
}
}
However, this doesn't work. You'll find that the mirror has a count
of 0
. I really think that this is an oversight on the part of the Swift team in their implementation of Swift._EnumMirror
, and I'll be filing a radar about this. rawValue
is definitely a legitimate property. It is a weird scenario because enums aren't allowed to have other stored properties. Also, your enum's declaration never explicitly conforms to RawRepresentable
, nor does it declare the rawValue
property. The compiler just infers that when you type enum MyEnum: String
or : Int
or whatever. In my tests, it appears that it shouldn't matter whether the property is defined in a protocol or is an instance of an associated type, it should still be a property represented in the mirror.
Allow for protocol types with defined associated types. As I mentioned in my comment above, it's a limitation in Swift that you cannot cast to a protocol type that has associated type requirements. You can't simply cast to RawRepresentable
because Swift doesn't know what type the rawValue
property will return. Syntax such as RawRepresentable<where RawValue == String>
is conceivable, or perhaps protocol<RawRepresentable where RawValue == String>
. If this were possible, you could try to cast to the type through a switch statement as in:
switch theEnum {
case let rawEnum as protocol<RawRepresentable where RawValue == String>:
return rawEnum.rawValue
// And so on
}
But that's not defined in Swift. And if you just try to cast to RawRepresentable
, the Swift compiler tells you that you can only use this in a generic function, but as I look at your code, that's only led you down a rabbit-hole. Generic functions need type information at compile-time in order to work, and that's exactly what you don't have working with Any
instances.
The Swift team could change protocols to be more like generic classes and structs. For example MyGenericStruct<MyType>
and MyGenericClass<MyType>
are legitimately specialized concrete types that you can make a runtime check on and cast to. However, the Swift team may have good reasons for not wanting to do this with protocols. Specialized versions of protocols (i.e. protocol references with known associated types) still wouldn't be concrete types. I wouldn't hold my breath for this ability. I consider this the weakest of my proposed solutions.
Extend protocols to conform to protocols. I really thought I could make this solution work for you, but alas no. Since Swift's built-in mirror for enums doesn't deliver on the rawValue
, I thought why not implement my own generic mirror:
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)
}
}
Great! Now all we have to do is extend RawRepresentable
to be Reflectable
so that we can first cast theEnum as Reflectable
(no associated type required) and then call reflect(theEnum)
to give us our awesome custom mirror:
extension RawRepresentable: Reflectable {
func getMirror() -> MirrorType {
return RawRepresentableMirror(self)
}
}
Compiler error: Extension of protocol 'RawRepresentable' cannot have
an inheritance clause
Whaaaat?! We can extend concrete types to implement new protocols, such as:
extension MyClass: MyProtocol {
// Conform to new protocol
}
As of Swift 2, we can extend protocols to give concrete implementations of functions, such as:
extension MyProtocol {
// Default implementations for MyProtocol
}
I thought for sure we could extend protocols to implement other protocols, but apparently not! I see no reason why we couldn't have Swift do this. I think this would very much fit in with the protocol-oriented programming paradigm that was the talk of WWDC 2015. Concrete types that implement the original protocol would get a new protocol conformance for free, but the concrete type is also free to define its own versions of the new protocol's methods. I'll definitely be filing an enhancement request for this because I think it could be a powerful feature. In fact, I think it could be very useful in your reflection library.
Edit: Thinking back on my dissatisfaction with answer 2, I think there's a more elegant and realistic possibility for working with these protocols broadly, and that actually combines my idea from answer 3 about extending protocols to conform to new protocols. The idea is to have protocols with associated types conforming to new protocols that retrieve type-erased properties of the original. Here is an example:
protocol AnyRawRepresentable {
var anyRawValue: Any { get }
}
extension RawRepresentable: AnyRawRepresentable {
var anyRawValue: Any {
return rawValue
}
}
Extending the protocol in this way wouldn't be extending inheritance per se. Rather it would be just saying to the compiler "Wherever there is a type that conforms to RawRepresentable
, make that type also conform to AnyRawRepresentable
with this default implementation." AnyRawRepresentable
wouldn't have associated type requirements, but can still retrieve rawValue
as an Any
. In our code, then:
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
}
}
This kind of solution could be used broadly with any kind of protocol with associated types. I will likewise be including this idea in my proposal to the Swift team for extending protocols with protocol conformance.
Update: None of the above options are available as of Swift 4. I did not receive a response as to why the Mirror
on a RawRepresentable
enum does not contain its rawValue
. As for options #2 and #3, they are still within the realm of possibility for future releases of Swift. They have been mentioned on the Swift mailing list and in the Generics Manifesto document authored by Doug Gregor of the Swift team.
The proper term for option #2 ("Allow for protocol types with defined associated types") is generalized existentials. This would allow protocols with associated types to possibly automatically return Any
where there is an associated type or allow for syntax like the following:
anyEnum as? Any<RawRepresentable where .RawValue == String>
Option #3 has been mentioned occasionally on the mailing list, and it is a commonly rejected requested feature, but that is not to say it couldn't be included in future versions of Swift. In the Generics Manifesto, it is termed "Conditional conformances via protocol extensions". While recognizing its power as a feature, it sadly also states that efficient implementation is "nearly impossible."