设想我们有这样一个需求,通过对于一副扑克牌的花色和牌面大小的 enum 类型,凑出一套不含大小王的扑克牌的数组。

扑克牌花色和牌面大小分别由下面两个 enum 来定义:

enum Suit: String {
    case Spades = "黑桃"
    case Hearts = "红桃"
    case Clubs = "草花"
    case Diamonds = "方片"
}

enum Rank: Int, Printable {
    case Ace = 1
    case Two, Three, Four, Five, Six, Seven, Eight, Nine, Ten
    case Jack, Queen, King
    var description: String {
        switch self {
        case .Ace:
            return "A"
        case .Jack:
            return "J"
        case .Queen:
            return "Q"
        case .King:
            return "K"
        default:
            return String(self.rawValue)
        }
    }
}

最容易想到的方式当然不外乎对两个 enum 进行两次循环,先循环取出 Suit 中的四种花色,然后在其中循环 Rank 类型取出数字后,就可以配合得到 52 张牌了。

在其他很多语言中,我们可以对 enum 类型或者其某个类似 values 的属性直接进行枚举,写出来的话,可能会是类似这样的代码:

for suit in Suit.values {
    for rank in Rank.values {
        // ...
        // 处理数据
    }
}

但是在 Swift 中,由于在 enum 中的某一个 case 中我们是可以添加具体值的 (就是 case Some(T) 这样的情况),因此直接使用 for...in 的方式在语义上是无法表达出所有情况的。不过因为在我们这个特定的情况中并没有带有参数的枚举类型,所以我们可以利用 static 的属性来获取一个可以进行循环的数据结构:

protocol EnumeratableEnumType {
    static var allValues: [Self] {get}
}

extension Suit: EnumeratableEnumType {
    static var allValues: [Suit] {
        return [.Spades, .Hearts, .Clubs, .Diamonds]
    }
}

extension Rank: EnumeratableEnumType {
    static var allValues: [Rank] {
        return [.Ace, .Two, .Three,
            .Four, .Five, .Six,
            .Seven, .Eight, .Nine,
            .Ten, .Jack, .Queen, .King]
    }
}

在这里我们使用了一个接口来更好地定义适用的接口。关于其中的 classstatic 的使用情景,可以参看这一篇总结。在实现了 allValues 后,我们就可以按照上面的思路写出:

for suit in Suit.allValues {
    for rank in Rank.allValues {
        print("\(suit.rawValue)\(rank)")
    }
}

// 输出:
// 黑桃A
// 黑桃2
// 黑桃3
// ...
// 方片K