Swift科普文《枚举(enum)》
Swift科普文《枚举(enum)》

Swift科普文《枚举(enum)》

什么是枚举

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() 直接调用,而不需要创建一个实例。

总结

在实际使用中,如果需要enum枚举可以在SwiftUI中绑定Picker等组件,需要enum枚举遵循String、CaseIterable和Identifiable协议:

enum PreviewMode: String, CaseIterable, Hashable, Identifiable {
    case window
    case quickLook

    var id: String { rawValue } // 使其可用于 List / Picker
}

否则,无法在SwiftUI组件或UserDefaults中使用rawValue存储原始值。

相关文章

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).")
}
   

如果您认为这篇文章给您带来了帮助,您可以在此通过支付宝或者微信打赏网站开发者。

欢迎加入我们的 微信交流群QQ交流群,交流更多精彩内容!
微信交流群二维码 QQ交流群二维码

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注