什么是枚举
Swift 中的枚举 (enum) 是一种强大的数据类型,可以定义一组相关的值,并为这些值提供更结构化和安全的操作方式。Swift 的枚举比许多其他语言中的枚举更灵活,因为它们可以:
1、关联值 – 枚举的每个成员可以存储不同类型的关联数据。
2、原始值 – 枚举的成员可以以某种默认类型(如整数、字符串)初始化。
3、方法 – 枚举可以定义方法,允许在枚举成员上调用。
定义一个简单的枚举
enum CompassDirection {
case north
case south
case east
case west
}
使用枚举:
var direction = CompassDirection.north
direction = .west
关联值
enum Barcode {
case upc(Int, Int, Int, Int)
case qrCode(String)
}
var productBarcode = Barcode.upc(8, 85909, 51226, 3)
productBarcode = .qrCode("ABCDEFGH")
可以使用 switch 语句处理关联值:
switch productBarcode {
case .upc(let numberSystem, let manufacturer, let product, let check):
print("UPC: \(numberSystem), \(manufacturer), \(product), \(check).")
case .qrCode(let code):
print("QR Code: \(code).")
}
原始值
可以为枚举提供默认的初始值,这种情况下称为原始值(Raw Values):
enum Planet: Int {
case mercury = 1, venus, earth, mars, jupiter, saturn, uranus, neptune
}
let earth = Planet(rawValue: 3) // 返回 .earth
在这个例子中,Planet 枚举的每个 case 都有一个整数作为原始值。因为 mercury 被显式设为 1,后续的 case 会依次自动递增:venus 为 2,earth 为 3,以此类推。
String原始值示例:
enum Compass: String {
case north = "North"
case south = "South"
case east = "East"
case west = "West"
}
let c = Compass.east
print(c.rawValue) // "East"
在这个示例中,为每一个枚举case单独设置默认值。当访问对应枚举默认值时,使用 rawValue 返回原始值。
访问原始值
可以通过 rawValue 属性访问枚举 case 的原始值。例如:
let planet = Planet.earth
print("Earth's raw value is \(planet.rawValue)") // 输出: Earth's raw value is 3
根据原始值初始化枚举实例
可以使用 init?(rawValue:) 通过原始值创建枚举实例。如果给定的原始值没有对应的枚举 case,则会返回 nil,这是一个可失败的初始化。
let raw = "South"
let compass = Compass(rawValue: raw) // 返回 Compass.south,类型是 Compass?
因此,常配合 ?? 使用:
let r = Rank(rawValue: 5) ?? .one // 如果失败就默认用 .one
使用场景
1、映射和转换:原始值使得枚举 case 和外部数据源(如数据库、API 或文件)之间的映射变得简单。例如,可以将 Planet 的原始值作为数据库中的 ID 存储,并在需要时通过 rawValue 将其还原为枚举 case。
2、简单的数据表示:有固定的整数、字符串或其他简单值时,可以将这些值直接映射为枚举的原始值,避免使用魔法数或硬编码的字符串。例如,用枚举表示 HTTP 状态码:
enum HTTPStatusCode: Int {
case ok = 200
case notFound = 404
case internalServerError = 500
}
let status = HTTPStatusCode(rawValue: 404)
if status == .notFound {
print("Page not found.")
}
3、自动增量值:如果需要为枚举 case 提供自动递增的数值(如计数器),可以从某个特定的值开始,并利用自动递增功能。例如,星期几的定义:
enum DayOfWeek: Int {
case sunday = 1, monday, tuesday, wednesday, thursday, friday, saturday
}
在自动增量值设置方面,当我们不给第一个case,而是给指定的case赋值时,后续的case会自动从这个值开始递增。
enum DayOfWeek: Int {
case sunday, monday, tuesday, wednesday = 4, thursday, friday, saturday
}
1、sunday 自动获得 0
2、monday 自动获得 1
3、tuesday 自动获得 2
4、wednesday 被你手动指定为 4
5、后面的 thursday 会自动递增为 5
6、friday 会是 6
7、saturday 会是 7
输出效果
print(DayOfWeek.sunday.rawValue) // 输出: 0
print(DayOfWeek.monday.rawValue) // 输出: 1
print(DayOfWeek.tuesday.rawValue) // 输出: 2
print(DayOfWeek.wednesday.rawValue) // 输出: 4
print(DayOfWeek.thursday.rawValue) // 输出: 5
print(DayOfWeek.friday.rawValue) // 输出: 6
print(DayOfWeek.saturday.rawValue) // 输出: 7
当手动指定 wednesday = 4 后,Swift 会从这个值开始向后递增计算。因此,这种方式适合希望有部分 case 手动设置值,其他 case 仍然使用自动递增的场景。
如果将 wednesday 的原始值设置为 0,那么 sunday 和 wednesday 都会有相同的原始值 0。这是允许的,但需要注意的是,这样会导致两个不同的 case 有相同的原始值,这可能会引发一些问题。
enum DayOfWeek: Int {
case sunday, monday, tuesday, wednesday = 0, thursday, friday, saturday
}
在这个枚举中:
1、sunday 的原始值自动设置为 0(因为它是第一个 case,自动递增从 0 开始)
2、wednesday 被手动设置为 0
3、monday 和 tuesday 仍然会自动递增,即 1 和 2
4、thursday, friday, 和 saturday 从 wednesday 的 0 开始自动递增,变为 1, 2, 3
输出效果
print(DayOfWeek.sunday.rawValue) // 输出: 0
print(DayOfWeek.monday.rawValue) // 输出: 1
print(DayOfWeek.tuesday.rawValue) // 输出: 2
print(DayOfWeek.wednesday.rawValue) // 输出: 0
print(DayOfWeek.thursday.rawValue) // 输出: 1
print(DayOfWeek.friday.rawValue) // 输出: 2
print(DayOfWeek.saturday.rawValue) // 输出: 3
注意事项
多个相同原始值:sunday 和 wednesday 具有相同的原始值 0。这是允许的,但如果你用 DayOfWeek(rawValue: 0) 初始化一个枚举实例时,Swift 只会返回找到的第一个匹配项。因此,在这个例子中,DayOfWeek(rawValue: 0) 会返回 .sunday,而不会返回 .wednesday。
if let day = DayOfWeek(rawValue: 0) {
print(day) // 输出: sunday
}
潜在问题:当有相同的原始值时,可能会引发歧义或错误结果。如果想使用 rawValue 从原始值创建枚举实例,就无法区分 sunday 和 wednesday,因为它们都有相同的 0。
无论如何,通过访问原始值,Swift 枚举不仅可以更好地表达常量,还可以方便地与外部系统或数据源进行交互。
枚举的高级用法
1、遵守协议(如:CaseIterable, Identifiable, Codable)
enum Animal: String, CaseIterable, Identifiable {
case dog, cat, bird
var id: String { self.rawValue }
}
这样可以:
ForEach(Animal.allCases) { animal in
Text(animal.rawValue)
}
2、计算属性
enum Planet: Int {
case earth = 3
case mars = 4
var description: String {
switch self {
case .earth: return "Third planet"
case .mars: return "Fourth planet"
}
}
}
3、自定义方法
enum Light {
case red, yellow, green
func next() -> Light {
switch self {
case .red: return .green
case .green: return .yellow
case .yellow: return .red
}
}
}
var currentLight = Light.red
print(currentLight) // 输出:red
currentLight = currentLight.next()
print(currentLight) // 输出:green
currentLight = currentLight.next()
print(currentLight) // 输出:yellow
在这个例子中,nextLight() 是一个实例方法,它基于当前信号灯的状态,返回下一个状态。
4、类型方法
enum MathConstants {
static func pi() -> Double {
return 3.14159
}
}
print(MathConstants.pi()) // 输出:3.14159
在这个例子中,pi() 是一个类型方法,可以通过 MathConstants.pi() 直接调用,而不需要创建一个实例。
相关文章
Swift生成枚举集合的CaseIterable协议:https://fangjunyu.com/2024/12/02/swift-%e7%94%9f%e6%88%90%e6%9e%9a%e4%b8%be%e9%9b%86%e5%90%88%e7%9a%84caseiterable%e5%8d%8f%e8%ae%ae/
扩展知识
枚举赋值语句
enum CompassDirection {
case north
case south
case east
case west
}
var direction = CompassDirection.north
direction = .west
给变量 direction 赋值为 .west 时,实际上是一种语法简化。由于 direction 的类型已经被明确为 CompassDirection,因此在后续的赋值中不需要再重复写出完整的 CompassDirection.west,可以直接使用 .west。
类型推断机制让代码更加简洁。当编译器已经知道 direction 是 CompassDirection 类型时,可以省略枚举类型的名称,只写出枚举的成员即可。具体如下:
var direction: CompassDirection = .north // 明确指定类型,使用 .north 简写
direction = .west // 可以省略类型名称,直接使用 .west
等价于:
var direction: CompassDirection = CompassDirection.north // 完整写法
direction = CompassDirection.west // 完整写法
这种语法简化在 Swift 中很常见,让代码看起来更简洁,同时减少冗余,尤其是当同一个枚举类型频繁使用时。
throws抛出的枚举错误
在Swift中,当throws方法抛出错误并切这些错误由枚举表示时,错误会以enum case的形式返回。通常枚举会遵循Error协议,从而成为可抛出的错误类型。
定义枚举错误类型
假设我们定义一个枚举来表示各种可能的错误:
enum FileError: Error {
case notFound
case insufficientPermissions
case unknownError
}
抛出错误
在使用 throws 的函数中,可以抛出这些枚举类型的错误:
func readFile(filename: String) throws {
if filename.isEmpty {
throw FileError.notFound
} else if filename == "restricted.txt" {
throw FileError.insufficientPermissions
} else {
// 正常读取文件逻辑
}
}
使用 do-catch 处理错误
在调用抛出错误的方法时,可以用 do-catch 语句来处理这些错误:
do {
try readFile(filename: "restricted.txt")
} catch FileError.notFound {
print("Error: File not found.")
} catch FileError.insufficientPermissions {
print("Error: Insufficient permissions to read the file.")
} catch {
print("Error: \(error).")
}