Can I define an enum as a subset of another enum's cases?

亡梦爱人 提交于 2019-12-05 03:04:27

No. This is currently not possible with Swift enums.

The solutions I can think of:

  • Use protocols as I outlined in your other question
  • Fallback to a runtime check

Here's a possible compile-time solution:

enum Direction: ExpressibleByStringLiteral {

  case unknown

  case left
  case right
  case up
  case down

  public init(stringLiteral value: String) {
    switch value {
    case "left": self = .left
    case "right": self = .right
    case "up": self = .up
    case "down": self = .down
    default: self = .unknown
    }
  }

  public init(extendedGraphemeClusterLiteral value: String) {
    self.init(stringLiteral: value)
  }

  public init(unicodeScalarLiteral value: String) {
    self.init(stringLiteral: value)
  }
}

enum HorizontalDirection: Direction {
  case left = "left"
  case right = "right"
}

enum VerticalDirection: Direction {
  case up = "up"
  case down = "down"
}

Now we can define a move method like this:

func move(_ allowedDirection: HorizontalDirection) {
  let direction = allowedDirection.rawValue
  print(direction)
}

The drawback of this approach is that you need to make sure that the strings in your individual enums are correct, which is potentially error-prone. I have intentionally used ExpressibleByStringLiteral for this reason, rather than ExpressibleByIntegerLiteral because it is more readable and maintainable in my opinion - you may disagree.

You also need to define all 3 of those initializers, which is perhaps a bit unwieldy, but you would avoid that if you used ExpressibleByIntegerLiteral instead.

I'm aware that you're trading compile-time safety in one place for another, but I suppose this kind of solution might be preferable in some situations.

To make sure that you don't have any mistyped strings, you could also add a simple unit test, like this:

XCTAssertEqual(Direction.left, HorizontalDirection.left.rawValue)
XCTAssertEqual(Direction.right, HorizontalDirection.right.rawValue)
XCTAssertEqual(Direction.up, VerticalDirection.up.rawValue)
XCTAssertEqual(Direction.down, VerticalDirection.down.rawValue)

You probably solved your issue, but to anyone looking for an answer, for some time now (not sure when Apple introduced it) you can use associated values inside enum cases to model these kinds of states.

enum VerticalDirection {
    case up
    case down
}

enum HorizontalDirection {
    case left
    case right
}

enum Direction {
    case vertical(direction: VerticalDirection)
    case horizontal(direction: HorizontalDirection)
}

So you can use a method like this:

func move(_ direction: Direction) {
    print(direction)
}

move(.horizontal(.left))

And if you conform to Equatable protocol:

extension Direction: Equatable {
    static func ==(lhs: Direction, rhs: Direction) -> Bool {
        switch (lhs, rhs) {
        case (.vertical(let lVertical), .vertical(let rVertical)):
            switch (lVertical, rVertical) {
            case (.up, .up):
                return true
            case (.down, .down):
                return true
            default:
                return false
            }
        case (.horizontal(let lHorizontal), .horizontal(let rHorizontal)):
            switch (lHorizontal, rHorizontal) {
            case (.left, .left):
                return true
            case (.right, .right):
                return true
            default:
                return false
            }
        default:
            return false
        }
    }
}

you can do something like this:

func isMovingLeft(direction: Direction) -> Bool {
    return direction == .horizontal(.left)
}

let characterDirection: Direction = .horizontal(.left)
isMovingLeft(direction: characterDirection) // true
isMovingLeft(direction: characterDirection) // false

Use Swift protocol OptionSet

struct Direction: OptionSet {
let rawValue: int
static let up = Direction(rawValue: 1<<0)
static let right = Direction(rawValue: 1<<1)
static let down = Direction(rawValue: 1<<2)
static let left = Direction(rawValue: 1<<3)
static let horizontal = [.left, .right]
static let vertical = [.up, down]
}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!