如何访问开关语句之外的 Swift 枚举关联值

考虑一下:

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


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

我如何访问,比如说,lefEdge的关联值,直接,而不使用开关语句?

let noIdeaHowTo          = leftEdge.associatedValue + 0.5

这根本不能编译!

我看了一下 这些 那么 问题,但似乎没有一个答案能解决这个问题。

上面的 noIdeaHowTo非编译行实际上应该是一行程序,但是因为 associated value可以是任何类型,我甚至看不到用户代码如何在 le enum 本身中编写一个“通用”get 或者 AssociatedValue 方法。

我最终得到了这个,但是它很恶心,每次我添加/修改一个案例时都需要重新检查代码..。

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
}
}
}
}

有什么建议吗?

45023 次浏览

I think you may be trying to use enum for something it was not intended for. The way to access the associated values is indeed through switch as you've done, the idea being that the switch always handles each possible member case of the enum.

Different members of the enum can have different associated values (e.g., you could have Diagonal(CGFloat, CGFloat) and Text(String) in your enum Line), so you must always confirm which case you're dealing with before you can access the associated value. For instance, consider:

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

How could you presume to get the associated value from myLine when you might be dealing with CGFloat, String, or two CGFloats? This is why you need the switch to first discover which case you have.

In your particular case it sounds like you might be better off with a class or struct for Line, which might then store the CGFloat and also have an enum property for Vertical and Horizontal. Or you could model Vertical and Horizontal as separate classes, with Line being a protocol (for example).

Why this is not possible is already answered, so this is only an advice. Why don't you implement it like this. I mean enums and structs are both value types.

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)

As others have pointed out, this is now kind of possible in 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

With Swift 2 it's possible to get the associated value (read only) using reflection.

To make that easier just add the code below to your project and extend your enum with the EVAssociated protocol.

    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)
}
}
}

Then you can access the .asociated value with code like this:

    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)
}

The code above is from my project https://github.com/evermeer/EVReflection https://github.com/evermeer/EVReflection

You can use a guard statement to access the associated value, like this.

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)

You can get the associated value without using a switch using the if case let syntax:

enum Messages {
case ping
case say(message: String)
}


let val = Messages.say(message: "Hello")


if case let .say(msg) = val {
print(msg)
}

The block inside the if case let will run if the enum value is .say, and will have the associated value in scope as the variable name you use in the if statement.