Swift — 枚举(Enumerations)

枚举(Enumeration)是 Swift 中的一种强大的数据类型,它允许你定义一组相关的值,并且每个值都被认为是同一类型的一部分。枚举可以帮助你组织代码,更好地表示和处理一组相关的值。

枚举的定义和使用 #

一个简单的枚举定义如下:

enum CompassPoint {
    case north
    case south
    case east
    case west
}

可以像这样声明一个枚举变量:

var direction = CompassPoint.north

你可以使用点语法来改变枚举的值:

direction = .east

关联值 #

枚举可以包含关联值,这使得枚举成员可以在其背后存储不同类型的关联值。关联值与枚举成员相关联,每个枚举成员的关联值类型可以各不相同。

enum Barcode {
    case upc(Int, Int, Int, Int)
    case qrCode(String)
}

var productBarcode = Barcode.upc(8, 85909, 51226, 3)
productBarcode = .qrCode("ABCDEFGHIJKLMNOP")

你可以使用 switch 语句来匹配枚举值并提取关联值:

switch productBarcode {
case .upc(let numberSystem, let manufacturer, let product, let check):
    print("UPC: \(numberSystem), \(manufacturer), \(product), \(check)")
case .qrCode(let productCode):
    print("QR Code: \(productCode)")
}

原始值 #

枚举成员可以被预填充默认值,称为原始值(raw values)。这些原始值的类型可以是字符串、字符或任何整型或浮点类型,每个原始值在其枚举声明中必须是唯一的。

enum Planet: Int {
    case mercury = 1, venus, earth, mars, jupiter, saturn, uranus, neptune
}

let earthsOrder = Planet.earth.rawValue // earthsOrder是3

你可以使用 rawValue 初始化一个枚举成员:

let possiblePlanet = Planet(rawValue: 3) // possiblePlanet 是 Planet.earth

枚举中的方法 #

枚举可以定义实例方法,方法可以访问枚举成员的值并运行一些逻辑:

enum CompassPoint {
    case north, south, east, west
    
    mutating func next() {
        switch self {
        case .north:
            self = .east
        case .east:
            self = .south
        case .south:
            self = .west
        case .west:
            self = .north
        }
    }
}

var direction = CompassPoint.north
direction.next()  // direction 现在是 .east

在这个例子中,next() 方法修改了 CompassPoint 枚举实例的值,并且因为它会修改实例,所以它被标记为 mutating

递归枚举 #

递归枚举是一种枚举类型,它有一个或多个枚举成员使用该类型的实例作为关联值。为了声明递归枚举,必须在枚举成员前使用 indirect 关键字。

enum ArithmeticExpression {
    case number(Int)
    indirect case addition(ArithmeticExpression, ArithmeticExpression)
    indirect case multiplication(ArithmeticExpression, ArithmeticExpression)
}

// 你也可以在枚举前加上 indirect 关键字
indirect enum ArithmeticExpression {
    case number(Int)
    case addition(ArithmeticExpression, ArithmeticExpression)
    case multiplication(ArithmeticExpression, ArithmeticExpression)
}

下面是如何使用递归枚举来定义和计算算术表达式的例子:

let five = ArithmeticExpression.number(5)
let four = ArithmeticExpression.number(4)
let sum = ArithmeticExpression.addition(five, four)
let product = ArithmeticExpression.multiplication(sum, ArithmeticExpression.number(2))

func evaluate(_ expression: ArithmeticExpression) -> Int {
    switch expression {
    case .number(let value):
        return value
    case .addition(let left, let right):
        return evaluate(left) + evaluate(right)
    case .multiplication(let left, let right):
        return evaluate(left) * evaluate(right)
    }
}

let result = evaluate(product) // result 是 18

使用注意 #

  1. 一旦确定变量的类型,再次为其赋值可以省略枚举类型名,使用更简短的点语法即可。
  2. 与其他语言的枚举不一样的是,swift 的枚举成员在创建时不会被赋予一个默认的整型值。
  3. switch 时 case 要覆盖全,或者用 default ,不然会报错。

小结 #

  • 定义枚举 使用 enum 关键字。
  • 基础用法:可以定义基本成员,并通过点语法访问和设置。
  • 关联值:允许枚举成员携带不同类型的值,增加灵活性。
  • 原始值:为枚举成员预定义原始值,可以是字符串、整型或浮点数。
  • 枚举方法:枚举可以包含方法,以增加行为逻辑。
  • 递归枚举:允许枚举成员包含自身类型的实例,用于构建复杂的数据结构。

枚举在 Swift 中是非常强大的工具,有助于创建更加严谨和类型安全的代码。

遍历 #

在 Swift 中,枚举是一种强大的数据结构,经常需要遍历它的所有可能值。要实现这一目的,需要让枚举遵循 CaseIterable 协议,该协议允许我们轻松地遍历枚举的所有 case

以下是关于如何遍历枚举的完整说明和场景示例。


1. 使用 CaseIterable 遍历枚举 #

示例代码 #

enum Weekday: CaseIterable {
    case monday, tuesday, wednesday, thursday, friday, saturday, sunday
}

for day in Weekday.allCases {
    print(day)
}

说明: #

  1. CaseIterable 协议

    • 让枚举遵循 CaseIterable 协议后,Swift 会自动生成一个静态变量 allCases,它是包含所有枚举值的数组。
    • allCases 可用于遍历枚举的所有情况。
  2. 输出: 运行上方代码,输出如下:

    monday
    tuesday
    wednesday
    thursday
    friday
    saturday
    sunday
    
  • 注意:
    所有枚举 case 必须没有关联值(即只能是简单的 case)。如果枚举带有关联值,则无法直接使用 CaseIterable

2. 手动实现 allCases 属性(带关联值的枚举) #

如果枚举带有关联值,你无法直接使用 CaseIterable 遍历所有情况。这时可以手动实现一个静态属性 allCases 来存储所有枚举值。

示例代码 #

enum Season {
    case spring
    case summer
    case autumn
    case winter
}

extension Season {
    static var allCases: [Season] {
        return [.spring, .summer, .autumn, .winter]
    }
}

for season in Season.allCases {
    print(season)
}

说明: #

  • 扩展手动实现:手动在扩展中定义 allCases 属性,列出所有可能的枚举值。
  • 输出
    spring
    summer
    autumn
    winter
    

3. 遍历带有原始值的枚举 #

如果枚举有 原始值(Raw Values)(如 StringInt),仍可以让其遵循 CaseIterable 并进行遍历。

示例代码 #

enum Sport: String, CaseIterable {
    case soccer = "Soccer"
    case basketball = "Basketball"
    case tennis = "Tennis"
    case baseball = "Baseball"
}

for sport in Sport.allCases {
    print("\(sport.rawValue)") // 访问枚举的原始值
}

输出#

Soccer
Basketball
Tennis
Baseball

4. 遍历带关联值的枚举(特殊处理) #

对于带 关联值 的枚举,无法直接用 CaseIterable 或预定义的方式列出所有可能情况。因此,你需要以不同方式手动实现遍历逻辑。

示例代码 #

enum Beverage {
    case coffee(size: String)
    case tea(flavor: String)
    case juice(type: String)
}

extension Beverage {
    static var allCases: [Beverage] {
        return [
            .coffee(size: "Small"),
            .coffee(size: "Medium"),
            .coffee(size: "Large"),
            .tea(flavor: "Green"),
            .tea(flavor: "Black"),
            .juice(type: "Apple"),
            .juice(type: "Orange")
        ]
    }
}

for beverage in Beverage.allCases {
    print(beverage)
}

说明: #

  • 手动列举所有可能的情况:因为带有关联值,所以需要手动列举这些组合作为 allCases 值。
  • 限制性:这种方法适合固定枚举值较少的场景,动态关联值场景需要更复杂的处理方式。

输出#

coffee(size: "Small")
coffee(size: "Medium")
coffee(size: "Large")
tea(flavor: "Green")
tea(flavor: "Black")
juice(type: "Apple")
juice(type: "Orange")

5. 用 Mirror 实现泛化遍历(实验性方法) #

如果你需要遍历枚举,而不想显式列出所有 case,可以尝试利用 Swift 的 Mirror 反射来识别枚举的所有可能值。

示例代码 #

enum Animal {
    case dog
    case cat
    case rabbit
    case bird
}

// 获取所有枚举的字符串值
func allCases<T: Hashable>(_ enumType: T.Type) -> [T] {
    var caseList: [T] = []
    let mirror = Mirror(reflecting: enumType)
    for child in mirror.children {
        if let value = child.value as? T {
            caseList.append(value)
        }
    }
    return caseList
}

// 使用
let animals = allCases(Animal.self)
print(animals)

说明: #

  • Mirror 是 Swift 提供的工具,可用于反射枚举结构,但反射可能会受到某些语法和场景的限制。
  • 此方法对 Swift 的未来版本稳定性有潜在风险,因此仅建议用于实验性场景。

6. 使用 Strideable 遍历带数值关联的枚举 #

对于关联值是数字(如 Int)类型的枚举,可以利用 Strideable 协议轻松实现自动遍历。

示例代码 #

enum Direction: Int, CaseIterable {
    case north = 0
    case east
    case south
    case west
}

for direction in Direction.allCases {
    print("\(direction) -> Raw Value: \(direction.rawValue)")
}

输出#

north -> Raw Value: 0
east -> Raw Value: 1
south -> Raw Value: 2
west -> Raw Value: 3

7. 为枚举增加便利方法 #

通过扩展枚举,可以为其增加便利方法,例如:

示例代码 #

enum GameLevel: Int, CaseIterable {
    case beginner = 1
    case intermediate
    case advanced
}

extension GameLevel {
    static func level(for rawValue: Int) -> GameLevel? {
        return GameLevel(rawValue: rawValue)
    }
}

if let level = GameLevel.level(for: 2) {
    print(level) // Output: intermediate
}

总结:哪种方式最适合? #

枚举类型遍历方式
无关联值的普通枚举使用 CaseIterable
带关联值(固定值情况)手动实现 allCases
带原始值(例如 IntString使用 CaseIterable
动态场景,关联值未知手动扩展或 Mirror

通过这些方法,你可以根据实际需求,选择在 Swift 中合适的方式来遍历枚举的所有值。

本文共 2326 字,上次修改于 Jan 22, 2025