在泛型函数中从枚举中获取rawValue [英] Get rawValue from enum in a generic function

查看:150
本文介绍了在泛型函数中从枚举中获取rawValue的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

更新8/28/2015:
这将在Swift 2中解决



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



更新10 / 23/2015:
使用Swift 2泛型,您仍然无法获取rawValue。您可以获得相关值。



原始问题:



我有一些通用反思代码用swift编写。在该代码中,我无法获取基于枚举的属性的值。问题归结于以下事实:我无法执行$ code> .rawValue ,该属性值类型为任何。 Swift反射代码将返回枚举的值作为类型 Any 。那么我如何从一个Any到一个AnyObject,它是枚举的rawValue。



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



有没有什么办法可以解决这个问题,而不是添加原始枚举的代码?



为我的反思代码我需要 getRawValue 方法签名保持原样。

 类解决方案测试:XCTestCase {
func testEnumToRaw(){
let test1 = getRawValue(MyEnumOne.OK)
XCTAssertTrue(test1 ==OK,可以使用通用函数获取原始值)
let test2 = getRawValue(MyEnumTwo.OK)
XCTAssertTrue(test2 ==1,可以使用通用函数获取原始值)
let test3 = getRawValue(MyEnumThree.OK )
XCTAssertTrue(test3 ==1,可以使用通用函数获取原始值)
}


枚举MyEnumOne:String,EVRawString {
case NotOK =NotOK
case OK =OK
}

枚举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 {
//我们可以使用反射:
let mirror = reflect(theEnum)
如果mirror.disposition == .Aggregate {
print(Disposition is .Aggregate \\\


// OK,现在?

// Thees不要编译:
//返回枚举值(rawValue:theEnum)
//返回枚举值Value2(theEnum)

如果let value =那个? EVRawString {
return value.rawValue
}
如果let value = theEnum as? EVRawInt {
return String(value.rawValue)
}
}
var valueType:Any.Type = mirror.valueType
print(valueType = \ )\\\

//没有这些帮助:
// var value = mirror.value - >只是enum本身
// var objectIdentifier = mirror.objectIdentifier - > nil
// var count = mirror.count - > 0
// var summary:String = mirror.summary - > (枚举值)
// var quickLookObject = mirror.quickLookObject - > nil

let toString:String =\(theEnum)
print(\(toString)\\\

return toString
}

func enumRawValue< E:RawRepresentable>(rawValue:E.RawValue) - > String {
let value = E(rawValue:rawValue)?. rawValue
返回\(value)
}

func enumRawValue2< T:RawRepresentable> (rawValue:T) - > String {
return\(rawValue.rawValue)
}

}

公共协议EVRawInt {
var rawValue:Int {get}
}
public protocol EVRawString {
var rawValue:String {get}
}
public protocol EVRaw {
var anyRawValue:AnyObject {get
}


解决方案

不幸的是,在这一点上,看起来很可能在斯威夫特,但是我已经考虑过你的问题了一段时间,我会提出三种方式,Swift团队可以帮你解决这个问题。


  1. 修复枚举的镜像。最简单的解决方案是我确信已经尝试过的解决方案。您正在尝试构建一个反思库,您想反映一个任何值,以查看它是否是一个枚举,如果是,您需要看看它是否具有原始值。可以通过以下代码访问 rawValue 属性

      let mirror = reflect(theEnum)// theEnum是任何类型
    为i在0 ..< mirror.count {
    如果mirror [i] .0 ==rawValue {
    switch mirror [i] .1.value {
    case let s as String:
    return s
    case let csc as CustomStringConvertible:
    return csc.description
    默认值:
    返回nil
    }
    }
    }


但是,这不行。你会发现镜子有一个计数 0 。我真的认为这是Swift团队执行 Swift._EnumMirror 的一个监督,我将提供一个关于这个的雷达。 rawValue 绝对是一个合法的财产。这是一个奇怪的场景,因为枚举不允许有其他存储的属性。此外,您的枚举声明从未明确符合 RawRepresentable ,也不声明 rawValue 属性。编译器推断当您键入枚举MyEnum:String :Int 或其他。在我的测试中,似乎属性是在协议中定义的还是相关类型的一个实例都不重要,它仍然是镜像中表示的属性。


  1. 允许具有定义关联类型的协议类型我上面的评论,这是Swift的限制,你不能转换为具有关联类型要求的协议类型。因为Swift不知道 rawValue 属性将返回的类型,所以不能简单地转换为 RawRepresentable 。可以考虑使用诸如 RawRepresentable<其中RawValue == String> 的语法,或者可能协议< RawRepresentable其中RawValue == String> 。如果这是可能的话,你可以尝试通过switch语句来转换类型:

     切换Enum {
    case让rawEnum作为协议< RawRepresentable其中RawValue == String> ;:
    返回rawEnum.rawValue
    //等等
    }
    pre>

但这并没有在Swift中定义。如果您只是尝试转换为 RawRepresentable ,则Swift编译器会告诉您,您只能在通用函数中使用此代码,但是,当我查看代码时,这仅仅是在你下了一个兔子洞。通用功能需要在编译时输入信息才能正常工作,这正是您没有使用任何实例。



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:任何{return realValue}
    var valueType:Any.Type {return T.self}
    var objectIdentifier:ObjectIdentifier ? {return nil}
    var disposition:MirrorDisposition {return .Enum}
    var count:Int {return 1}

    下标(index:Int) - > (String,MirrorType){
    开关索引{
    case 0:
    return(rawValue,reflect(realValue.rawValue))
    默认值:
    fatalError索引超出范围)
    }
    }

    var summary:String {
    returnRaw Representable Enum:\(realValue)
    }

    var quickLookObject:QuickLookObject? {
    return QuickLookObject.Text(summary)
    }
    }




    1. 很棒!现在我们要做的是将 RawRepresentable 延伸到可反映,以便我们可以先转换 theEnum as Reflectable (不需要相关联的类型),然后调用 reflect(theEnum)给我们我们的真棒自定义镜像:

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




      编译器错误:扩展协议'RawRepresentable'不能有
      一个继承子句


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

       扩展MyClass:MyProtocol {
      //符合新协议
      }

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

       扩展MyProtocol {
      // MyProtocol的默认实现
      }

      我以为我们可以扩展协议来实现其他协议,但显然不是!我没有理由为什么我们不能让Swift做到这一点。我认为这将非常适合使用面向协议的编程范例,这是WWDC 2015的讨论。实现原始协议的具体类型将获得新的协议一致性,但具体类型也可以自由定义新协议的方法的版本。我一定会提出一个增强请求,因为我认为这可能是一个强大的功能。事实上,我认为这可能在你的反思图书馆里非常有用。



      编辑:回想起我对答案2的不满有一个比较优雅和现实的广泛使用这些协议的可能性,实际上结合了我的想法从答案3关于扩展协议,以符合新的协议。这个想法是具有符合新协议的关联类型的协议,以检索原始文件的类型删除属性。以下是一个例子:

       协议AnyRawRepresentable {
      var anyRawValue:Any {get}
      }

      extension RawRepresentable:AnyRawRepresentable {
      var anyRawValue:Any {
      return rawValue
      }
      }

      以这种方式扩展协议不会扩展继承本身。相反,它只是对编译器说无论何种类型符合 RawRepresentable ,使该类型也符合 AnyRawRepresentable 与此默认实现。 AnyRawRepresentable 将不具有关联的类型要求,但仍可以将 rawValue 作为任何。在我们的代码中,然后:

       如果let anyRawEnum = theEnum as? AnyRawRepresentable {//可以转换为
      让anyRawValue = anyRawEnum.anyRawValue // anyRawValue的类型为Any
      切换anyRawValue {
      case let s as String:
      return s
      case let csc as CustomStringConvertible:
      return csc.description
      default:
      return nil
      }
      }
      / pre>

      这种解决方案可以广泛地用于任何类型的关联协议。我也将把这个想法纳入我对Swift团队的建议,以扩展协议一致性协议。


      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.

      1. 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.

      1. 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.

      1. 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.

      这篇关于在泛型函数中从枚举中获取rawValue的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆