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

    相关文章

    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交流群二维码

    发表回复

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