美文网首页Swift编程Swift
Swift-细说枚举(Enum)

Swift-细说枚举(Enum)

作者: Mccc_ | 来源:发表于2020-04-07 14:49 被阅读0次

一. 枚举的声明

枚举的定义
enum SomeEnumeration {
    // 用case修饰枚举项
    case item
}

声明一个方向的枚举,包含四个枚举值: 东/西/南/北.

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

let east = Direction.east
var head: Direction = .west
head = .north

Swift 的枚举成员在被创建时本身就是完备的值,这些值的类型是已经明确定义好的 Direction 类型. 不会像 Objective-C 一样被赋予一个默认的整型值。在上面的 Direction 例子中,east、west 、north、south不会被隐式地赋值为 0,1,2 和 3。

枚举命名和使用注意点:
  • 枚举的第一个字母要大写,枚举值的第一个字母要小写;
  • 枚举类型起一个单数名字而不是复数名字,从而使得它能够顾名思义;
  • 一旦枚举类型确定,建议使用点语法设置值;

二. 枚举的特性

  • Swift 中的枚举成员类型: 字符串、字符、任意的整数值或浮点类型。
  • Swift 中的枚举成员可以指定任意类型的值来与不同的成员值关联储存(合集)。
  • Swift 中的枚举是具有自己权限的一类类型(计算属性、实例方法、扩展等)。

三. 枚举值相关

1. 枚举值的类型

Swift 中的枚举更加灵活,不必给每一个枚举成员提供一个值。如果给枚举成员提供一个值(称为原始值),则该值的类型可以是:字符串、字符、任意的整数值或浮点类型。

2. 原始值

Swift的枚举类型提供了一个叫原始值(RawValues)的实现,它为枚举项提供了一个默认值,这个默认值在编译期间就是确定的。

  • 获取原始值
let raw = Direction.west.rawValue
  • 通过原始值生成枚举项
let east1 = Direction.init(rawValue: "east")
print(type(of: east1))     
// Optional<Direction>

通过原值值获取的枚举项是可选类型的,所以需要用if let 做一下判断。

if let east = Direction.init(rawValue: "east") {     
    print(type(of: east))     
}
// Direction
3. 隐式原始值和显式原始值

未指定枚举值类型

enum Shape {
    case circle
    case square
    case oval
    case trilateral
}

// 不能给未指定类型的枚举成员提供值(case north = "north"❌)
error:  Enum case cannot have a raw value if the enum does not have a raw type
// 未指定类型的枚举成员无法使用rawValue属性(direction.rawValue❌)
error: Value of type 'Direction' has no member 'rawValue'

隐式原始值

enum Shape1: String {
    case circle  
    case square
    case oval
    case trilateral
}

隐式原始值下枚举项使用系统的分配值:

  • 如果是Sting类型,枚举值就是枚举项 case circle 等同于case circle = "circle"
  • 如果是Int类型,第一个枚举值默认是0,其他逐级+1
  • 如果是CGFloat等浮点类型,第一个枚举值默认是0.0,其他逐级+1

显式原始值

enum Shape2: String {
    case circle = "圆形"
    case square = "正方形"
    case oval = "椭圆"
    case trilateral = "三角形"
}

隐式 + 显式原始值

enum Shape4: Int {
    case circle = 1   
    case square = // 默认值是2
    case oval  // 默认值是3
    case trilateral  // 默认值是4
}

enum Shape4: Int {
    case circle   // 默认值是0
    case square = 2
    case oval  // 默认值是3
    case trilateral  // 默认值是4
}
2. 监听枚举值
var direction = Direction.east {
    willSet {
        if newValue != direction {
            print("方向即将发生变化")
        }
    }
    
    didSet {
        if oldValue != direction {
            print("方向已经发生变化")
        }
    }
}

这里使用了属性观察者,Swift里面的枚举更像一个对象,因此可以很方便的使用属性观察者对枚举值进行监听。

3. 关联值类型(Associated Values)

在 Swift 中,还可以定义这样的枚举类型,它的每一个枚举项都有一个附加信息,来扩充这个枚举项的信息表示,这又叫做关联值。

enum Trade {
    case buy(count: Int)
    case sell(count: Int)
}
let trade = Trade.buy(count: 10)

一定要学会使用关联值,这一点很重要。具体的使用后面的第九节会细说

4. 关联值相等判断

通常情况下枚举是很容易进行相等判断的。一旦为枚举增加了关联值,Swift就没法正确的比较了,需要自己对该枚举实现 ==运算符。

// 一个交易的枚举
enum Trade {
    case buy(count: Int)
    case sell(count: Int)
}

// 只有枚举值并且关联值一直才算相等。
func == (before: Trade, after: Trade) -> Bool {
   switch (before, after) {
     case let (.buy(count1), .buy(count2)) where count1 == count2:
       return true
     case let (.sell(count1), .sell(count2)) where count1 == count2:
       return true
     default:
        return false
   }
}

let trade1 = Trade.buy(count: 10)
let trade2 = Trade.sell(count: 10)
        
if trade1 == trade2 {
      print("一样的")
} else {
     print("不一样")
}
5. 有关联值的枚举排序/比较大小
enum SortEnum: Comparable {
    
    case a(Int)
    case b(String)
    indirect case c(SortEnum)
    
    var ordering: Int {
        switch self {
        case .a(_):
            return 0
        case .b(_):
            return 1
        case .c(_):
            return 2
        }
    }
    
    static func < (lhs: SortEnum, rhs: SortEnum) -> Bool {
        lhs.ordering < rhs.ordering
    }
    static func > (lhs: SortEnum, rhs: SortEnum) -> Bool {
        lhs.ordering > rhs.ordering
    }
}

let a = SortEnum.a(0)
let b = SortEnum.b("b")
let c = SortEnum.c(a)
let sortArr: [SortEnum] = [b, c, a]

print(sortArr.sorted())

借助Comparable协议,来实现枚举的比较。

四. 枚举与 switch

switch head {
case .east:
      print("东方向")
case .north, .south, .west:
      print("非东方向")
}

五. 枚举的遍历

enum Shape5: CaseIterable {
    case circle
    case square
    case oval
    case trilateral
}

for item in Shape5.allCases {
    print(item)
}

let count = Shape5.allCases.count

遵守CaseIterable协议的swift枚举是可以遍历的,通过allCases获取所有的枚举成员.

五. 枚举的嵌套

app里面的接口地址,如果都放一起,命名或者寻找都不方便,可以用枚举的嵌套来设计。可以分散在多个文件中,方便维护和管理。

/// 总接口
enum Interface { }

extension Interface {
    // 系统相关接口
    enum System {
        case sendMessage
        case uploadImage
    }
}

extension Interface {
    // 用户相关接口
    enum User {
        case name
        case icon
        case phone
    }
}

Interface.User.login

用枚举来管理系统里面的常量是一个不错的选择。

六. 枚举的(计算)属性和方法

1. 计算属性
enum AppleDeivce {
    case iPad, iPhone
    
    // 不能使用存储属性
    // var deviceName: String? ❌
    // error: Enums must not contain stored properties

    var yeaer: Int {
        switch self {
        case .iPad:
            return 2010
        case .iPhone:
            return 2007
        }
    }
}
let _ = AppleDeivce.iPhone.yeaer

枚举中不能使用存储属性,但是可以使用计算属性,计算属性的内容是在枚举值或者枚举关联值中得到的。

2. 实例方法
enum AppleDeivce {
    case iPad, iPhone, iWatch, other
    
     func introduced() -> String {
        switch self {
        case .iPad:
            return "iPad"
        case .iPhone:
            return "iPhone"
        case .iWatch:
            return "iWatch"
        default:
            return "other"
        }
    }
}
let _ = AppleDeivce.iPhone.introduced()

在这里,可以认为枚举是一个类,introduced是一个成员方法,AppleDeivce.iPhone 就是一个AppleDeivce的实例,case们是它的属性。introduced里面的switch self,其实就是遍历这个匿名属性的所有场景,如iPad,iPhone等,然后根据不同的场景返回不同的值。

3. 枚举的静态方法
enum AppleDeivce {
    case iPad, iPhone
    
    static func fromSlang(slang: String) -> AppleDeivce? {
        if slang == "iPhone" {
            return .iPhone
        }
        return nil
    }
}
let _ = AppleDeivce.fromSlang(slang: "iPhone")

可以做枚举的自定义构造方法。

七. 枚举与协议

系统的打印协议

public protocol CustomStringConvertible {
    var description: String { get }
}

让枚举遵守这个协议

enum AppleDeivce: CustomStringConvertible {
    case iPad, iPhone
    
    // 实现协议属性 
    var description: String {
        switch self {
        case .iPhone:
            return "要出现款iPhone了"
        default:
            return "iPad"
        }
    }
}
let _ = AppleDeivce.iPhone.description

八. 枚举与扩展

枚举可以进行扩展。可以将枚举中的case与method/protocol分隔开,阅读者可以快速消化枚举的内容。

enum AppleDeivce {
    case iPad, iPhone
}

extension AppleDeivce: CustomStringConvertible {
    var description: String {
        switch self {
        case .iPhone:
            return "要出现款iPhone了"
        default:
            return "iPad"
        }
    }
}

extension AppleDeivce {
    static func fromSlang(slang: String) -> AppleDeivce? {
        if slang == "iPhone" {
            return .iPhone
        }
        return nil
    }
}

extension AppleDeivce {
    func introduced() -> String {
        switch self {
        case .iPad:
            return "iPad"
        case .iPhone:
            return "iPhone"
        }
    }
}

extension AppleDeivce {
    var yeaer: Int {
        switch self {
        case .iPad:
            return 2010
        case .iPhone:
            return 2007
        }
    }
}

九. 枚举与泛型

设计一个网络类下的错误信息的处理功能。

  • 先声明一个错误信息的结构体,包含两个属性code码和错误信息
struct ErrorMessage {
    var code : Int
    var message : String
}
  • 声明网络错误的枚举
    该枚举接受一个泛型,用来接受关联值。
enum NetworkError<T> {
    case codeError(T)
    case wrongReturn
    case networkNull
    case tokenOverdue
}
  • 假如在网络类有一个这样的枚举项通过闭包返回出去了
let errorMessage = ErrorMessage.init(code: 404, message: "not found")
let codeError = NetworkError.codeError(errorMessage)
  • 调用网络api处,对网络错误处理
switch codeError {
case .codeError(let message):
      print(message.code)
      print(message.message)
default:
       break
}

十. 枚举与结构体

在项目中经常使用UserDefaults来存储一下简单的用户信息。但是对Key的维护不会很方便。而且会想不起来。用枚举+结构体就能很好的解决这个问题。

  • 声明协议
public protocol UserDefaultsSettable {
    associatedtype defaultKeys: RawRepresentable
}

extension UserDefaultsSettable where defaultKeys.RawValue == String {
    static public func set(value: Any, forKey key: defaultKeys) {
        let aKey = key.rawValue
        UserDefaults.standard.set(value, forKey: aKey)
    }
    
    static public func getString(forKey key: defaultKeys) -> String? {
        let aKey = key.rawValue
        let value = UserDefaults.standard.string(forKey: aKey)
        return value
    }
}
  • 给UserDefaults添加扩展
public extension UserDefaults {

    struct Version : UserDefaultsSettable {
        public enum defaultKeys: String {
            case version
            case build
        }
    }

    struct LocationInfo: UserDefaultsSettable {
        public enum defaultKeys: String {
            case latitude
            case longitude
        }
    }
}
// 可以由大到小逐级查询,更符合认知习惯。
UserDefaults.Version.set(value: "version", forKey: .version)
let version = UserDefaults.Version.getString(forKey: .version)

这么设计APP的存储模块,是不是更有层级感,更加方便使用呢?

十一. 递归枚举

递归枚举是拥有另一个枚举作为枚举成员关联值的枚举。当编译器操作递归枚举时必须插入间接寻址层。可以在声明枚举成员之前使用 indirect关键字来明确它是递归的。 也可以声明在整个枚举前,让所有的枚举成员都是递归的。

enum Arithmetic {
    /// 单一数字表达式
    case number(Int)
    /// 两个表达式的加法
    indirect case add(Arithmetic, Arithmetic)
    /// 两个表达式的减法
    indirect case subtract(Arithmetic, Arithmetic)
    /// 两个表达式的乘法
    indirect case multiply(Arithmetic, Arithmetic)
    /// 两个表达式的除法
    indirect case divide(Arithmetic, Arithmetic)
    
    /// 表达式的类型转换的函数
    func evaluate() -> Double {
        
        switch self {
        case .number(let value):
            return Double(value)
            
        case .add(let item1,let item2):
            return item1.evaluate() + item2.evaluate()
            
        case .subtract(let item1,let item2):
            return item1.evaluate() - item2.evaluate()
            
        case .multiply(let item1,let item2):
            return item1.evaluate() * item2.evaluate()
            
        case .divide(let item1,let item2):
            
            // 除数为0的处理
            if item2.evaluate() == 0 { return Double(Int.min) }
            return item1.evaluate() / item2.evaluate()
        }
    }
}

let two = Arithmetic.number(2)
let four = Arithmetic.number(4)
let five = Arithmetic.number(5)
let sum = Arithmetic.add(five, four)
let product = Arithmetic.multiply(sum, two)

print(two.evaluate())
print(sum.evaluate())
print(product.evaluate())

// 打印值分别为:2 9 18

相关文章

  • Swift-细说枚举(Enum)

    一. 枚举的声明 枚举的定义 声明一个方向的枚举,包含四个枚举值: 东/西/南/北. Swift 的枚举成员在被创...

  • Swift-枚举 enum

    本文主要介绍enum的常见使用形式,1、常规枚举写法 2、字符串类型枚举 枚举值和原始值rawValue的关系为c...

  • Swift-进阶:枚举enum

    本文主要介绍enum的常见使用形式,以及枚举大小是如何计算的 补充:添加脚本自动生成SIL 通过target ->...

  • swift3语法(八)

    枚举/结构体 枚举 定义形式enum 枚举名 { 枚举值} // 例如enum CompassPoint {...

  • Swift-进阶 08:枚举enum

    Swift 进阶之路 文章汇总[https://www.jianshu.com/p/5fbedf309237] 本...

  • Swift-进阶 08:枚举enum

    Swift 进阶之路 文章汇总[https://www.jianshu.com/writer#/notebooks...

  • Rust 编程语言-6-枚举和模式匹配

    6. Enum和Pattern matching 6.1 Enum枚举 IP地址枚举 消息类型枚举:可以看到枚举中...

  • 菜鸡学Swift3.0 12.枚举

    1. 枚举定义 enum 定义枚举类型 { case 枚举项 ... } 2.原始值 rawValue enum...

  • Swift Day03 枚举 可选项

    一、枚举 1. 枚举(enum)的基本写法? 2. 枚举(enum)的关联值(Associated Value)?...

  • Python_枚举

    一、枚举 Enum类 Python中所有枚举类型都是enum模块下的Enum类的子类。 枚举中的标识最好全部使用大...

网友评论

    本文标题:Swift-细说枚举(Enum)

    本文链接:https://www.haomeiwen.com/subject/xgnwuhtx.html