枚举(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
使用注意 #
- 一旦确定变量的类型,再次为其赋值可以省略枚举类型名,使用更简短的点语法即可。
- 与其他语言的枚举不一样的是,swift 的枚举成员在创建时不会被赋予一个默认的整型值。
- 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)
}
说明: #
CaseIterable
协议:- 让枚举遵循
CaseIterable
协议后,Swift 会自动生成一个静态变量allCases
,它是包含所有枚举值的数组。 allCases
可用于遍历枚举的所有情况。
- 让枚举遵循
输出: 运行上方代码,输出如下:
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)(如 String
或 Int
),仍可以让其遵循 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 |
带原始值(例如 Int 或 String ) | 使用 CaseIterable |
动态场景,关联值未知 | 手动扩展或 Mirror |
通过这些方法,你可以根据实际需求,选择在 Swift 中合适的方式来遍历枚举的所有值。