【问题标题】:How to access a Swift enum associated value outside of a switch statement如何在 switch 语句之外访问 Swift 枚举关联值
【发布时间】:2015-09-30 06:53:52
【问题描述】:

考虑:

enum Line {
    case    Horizontal(CGFloat)
    case    Vertical(CGFloat)
}

let leftEdge             =  Line.Horizontal(0.0)
let leftMaskRightEdge    =  Line.Horizontal(0.05)

如何在不使用 switch 语句的情况下直接访问 lefEdge 的关联值?

let noIdeaHowTo          = leftEdge.associatedValue + 0.5

这甚至无法编译!

我查看了theseSOquestions,但似乎没有一个答案能解决这个问题。

上面的noIdeaHowTo 非编译行应该是那一行,但是因为associated value 可以是任何类型,我什至看不到用户代码如何编写“通用”get 或 associatedValue 方法le 枚举本身。

我最终得到了这个,但这很糟糕,每次添加/修改案例时都需要我重新访问代码...

enum Line {
    case    Horizontal(CGFloat)
    case    Vertical(CGFloat)

    var associatedValue: CGFloat {
        get {
            switch self {
                case    .Horizontal(let value): return value
                case    .Vertical(let value): return value
            }
        }
    }
}

任何指针任何人?

【问题讨论】:

  • 我认为您所做的是正确的(即计算属性)。枚举可以具有任何类型的关联值,更重要的是任何计数。我看不出 Apple 如何在不强迫我们使用大量 as?if 的情况下提供对它们的速记访问。
  • 顺便说一句,如果您的枚举具有与每个案例相关联的 CGFloat,您可以考虑使用原始值而不是关联。
  • @AntonBronnikov 原始值是恒定的,它们必须是唯一的,也就是说,您不能有两个具有不同值的 Horizontal 实例。
  • 是的。然后计算属性。
  • 在 Swift 2 中,您可以在比 switch 更紧凑的if 中执行此操作:if case .Horizontal(let value) = leftEdge { print("value = \(value)") }

标签: swift enums


【解决方案1】:

您可以使用保护语句来访问关联的值,如下所示。

enum Line {
    case    Horizontal(Float)
    case    Vertical(Float)
}

let leftEdge             =  Line.Horizontal(0.0)
let leftMaskRightEdge    =  Line.Horizontal(0.05)

guard case .Horizontal(let leftEdgeValue) = leftEdge else { fatalError() }

print(leftEdgeValue)

【讨论】:

    【解决方案2】:

    在 Swift 2 中,可以使用反射获取关联的值(只读)。

    为了更简单,只需将以下代码添加到您的项目中,并使用 EVAssociated 协议扩展您的枚举。

        public protocol EVAssociated {
        }
    
        public extension EVAssociated {
            public var associated: (label:String, value: Any?) {
                get {
                    let mirror = Mirror(reflecting: self)
                    if let associated = mirror.children.first {
                        return (associated.label!, associated.value)
                    }
                    print("WARNING: Enum option of \(self) does not have an associated value")
                    return ("\(self)", nil)
                }
            }
        }
    

    然后您可以使用以下代码访问 .asociated 值:

        class EVReflectionTests: XCTestCase {
                func testEnumAssociatedValues() {
                    let parameters:[EVAssociated] = [usersParameters.number(19),
    usersParameters.authors_only(false)]
                let y = WordPressRequestConvertible.MeLikes("XX", Dictionary(associated: parameters))
                // Now just extract the label and associated values from this enum
                let label = y.associated.label
                let (token, param) = y.associated.value as! (String, [String:Any]?)
    
                XCTAssertEqual("MeLikes", label, "The label of the enum should be MeLikes")
                XCTAssertEqual("XX", token, "The token associated value of the enum should be XX")
                XCTAssertEqual(19, param?["number"] as? Int, "The number param associated value of the enum should be 19")
                XCTAssertEqual(false, param?["authors_only"] as? Bool, "The authors_only param associated value of the enum should be false")
    
                print("\(label) = {token = \(token), params = \(param)")
            }
        }
    
        // See http://github.com/evermeer/EVWordPressAPI for a full functional usage of associated values
        enum WordPressRequestConvertible: EVAssociated {
            case Users(String, Dictionary<String, Any>?)
            case Suggest(String, Dictionary<String, Any>?)
            case Me(String, Dictionary<String, Any>?)
            case MeLikes(String, Dictionary<String, Any>?)
            case Shortcodes(String, Dictionary<String, Any>?)
        }
    
        public enum usersParameters: EVAssociated {
            case context(String)
            case http_envelope(Bool)
            case pretty(Bool)
            case meta(String)
            case fields(String)
            case callback(String)
            case number(Int)
            case offset(Int)
            case order(String)
            case order_by(String)
            case authors_only(Bool)
            case type(String)
        }
    

    以上代码来自我的项目https://github.com/evermeer/EVReflection https://github.com/evermeer/EVReflection

    【讨论】:

      【解决方案3】:

      正如其他人所指出的,现在这在 Swift 2 中是可能的:

      import CoreGraphics
      
      enum Line {
          case    Horizontal(CGFloat)
          case    Vertical(CGFloat)
      }
      
      let min = Line.Horizontal(0.0)
      let mid = Line.Horizontal(0.5)
      let max = Line.Horizontal(1.0)
      
      func doToLine(line: Line) -> CGFloat? {
          if case .Horizontal(let value) = line {
              return value
          }
          return .None
      }
      
      doToLine(min) // prints 0
      doToLine(mid) // prints 0.5
      doToLine(max) // prints 1
      

      【讨论】:

      • 您将如何以适用于任何枚举和任何情况的方式实现它?
      • @RodrigoRuiz 我为您推荐 Arkku 的答案。您需要一个包含枚举和值的结构,而不仅仅是枚举。
      • 只是要指出:这与原始的switch 解决方案没有根本的不同,也就是说,您可以将if case 重写为switch,并使用default 的情况@ 987654326@ 获得完全相同的结果。这两种解决方案都要求您分别添加对不同情况的支持,并且不提供访问“任何情况”的关联值的通用方法。我仍然强烈建议使用 structclass 来解决一般问题,其中包含简单的“类型”enum 和单独的值。
      • 我在使用 Swift 5 时遇到语法错误 Pattern cannot match values of type 'EnumName'
      【解决方案4】:

      为什么这是不可能的已经回答了,所以这只是一个建议。你为什么不这样实现它。我的意思是枚举和结构都是值类型。

      enum Orientation {
          case Horizontal
          case Vertical
      }
      
      struct Line {
      
          let orientation : Orientation
          let value : CGFloat
      
          init(_ orientation: Orientation, _ value: CGFloat) {
      
              self.orientation = orientation
              self.value = value
          }
      } 
      
      let x = Line(.Horizontal, 20.0)
      
      // if you want that syntax 'Line.Horizontal(0.0)' you could fake it like this
      
      struct Line {
      
          let orientation : Orientation
          let value : CGFloat
      
          private init(_ orientation: Orientation, _ value: CGFloat) {
      
              self.orientation = orientation
              self.value = value
          }
      
          static func Horizontal(value: CGFloat) -> Line { return Line(.Horizontal, value) }
          static func Vertical(value: CGFloat) -> Line { return Line(.Vertical, value) }
      }
      
      let y = Line.Horizontal(20.0)
      

      【讨论】:

        【解决方案5】:

        我认为您可能正在尝试将enum 用于它不适合的用途。访问关联值的方法确实是通过 switch 就像您所做的那样,其想法是 switch 始终处理 enum 的每个可能的成员案例。

        enum 的不同成员可以有不同的关联值(例如,您的enum Line 中可能有Diagonal(CGFloat, CGFloat)Text(String)),因此您必须始终确认您处理的是哪种情况才可以访问关联的值。例如,考虑:

        enum Line {
            case Horizontal(CGFloat)
            case Vertical(CGFloat)
            case Diagonal(CGFloat, CGFloat)
            case Text(String)
        }
        var myLine = someFunctionReturningEnumLine()
        let value = myLine.associatedValue // <- type?
        

        当您可能与CGFloatString两个 CGFloats 打交道时,您如何假定从myLine 获取关联值?这就是为什么您需要switch 来首先发现您拥有哪个case

        在您的特定情况下,听起来您可能会更好地使用classstructLine,然后可能存储CGFloat 并且还具有Verticalenum 属性和Horizontal。或者您可以将VerticalHorizontal 建模为单独的类,Line 是一个协议(例如)。

        【讨论】:

        • 是的。我绝对认为这需要语言支持。这是一种情况,返回值不应该是关于不同的类型,而应该是不同的数量。可能是一个不同的计数元组,例如 case x ... return (1) case y ... return (0, "stuff") case z ... return (-1, 3.14, ["for", "good" , “措施”])。这可能最好让编译器直接支持。在途中闻到雷达的味道......
        • @verec 我认为语言支持的正确答案是保持关联值不变,但允许将存储的属性与关联值分开添加到枚举中。即:enum Line { case horizontal, vertical; var value: CGFloat } - 基本上是生成struct 的语法糖,这是目前最好的解决方案。如果关联的值和存储的属性相互排斥,则init(value: CGFloat) 的使用将类似于var line: Line = .horizontal(value: 1)
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-03-02
        • 2017-12-13
        • 1970-01-01
        相关资源
        最近更新 更多