Swift 中如何高效地使用枚举来处理不同的状态和事件?

126 阅读5分钟

Swift 中如何高效地使用枚举来处理不同的状态和事件? 在Swift中,枚举是处理不同状态和事件的理想选择,因为它们让相关联的值和逻辑组织在一起,清晰而且类型安全。高效使用枚举的方法包括:

1、 利用枚举的关联值来存储与每个枚举案例相关的额外信息。这使得枚举可以表达更复杂的状态或事件,同时保持代码整洁和组织良好。

2、 使用枚举来定义一组相关的命令或操作,然后通过switch语句来匹配并执行相应的逻辑。这种方式使得新增或修改命令变得非常简单。

3、 结合使用枚举和协议,可以定义一组遵循共同协议的枚举,这样即使它们代表不同的状态或事件,也能以统一的方式处理。

4、 利用枚举的原始值(通常用于表示静态或不变的数据)和计算属性,可以为枚举值附加更多的上下文信息,增加代码的可读性和易用性。

通过这些方法,枚举成为了Swift中管理状态和事件的强大工具,既增强了代码的表达能力,又保持了严格的类型安全。

常见的用法

  • 关联值(Associated Values):枚举的成员可以关联一个或多个值。这样可以给枚举成员提供更多的信息,并且可以根据不同的关联值来执行不同的逻辑操作。
    示例:
enum Status {
    case success
    case failure(errorCode: Int)
    case loading(progress: Float)
}
 
let success = Status.success
let failure = Status.failure(errorCode: 404)
let loading = Status.loading(progress: 0.5)
  • 原始值(Raw Values):枚举的成员可以使用原始值来进行比较和判断。原始值可以是字符串、整数、浮点数等。
    示例:
enum Direction: String {
    case north = "N"
    case south = "S"
    case east = "E"
    case west = "W"
}
 
let direction = Direction.north
print(direction.rawValue) // 输出 "N"
  • 递归枚举(Recursive Enumerations):枚举的成员可以是自身类型的关联值。递归枚举通常用于描述具有递归结构的数据类型,例如树或链表。
    示例:
enum LinkedList<T> {
    case empty
    indirect case node(value: T, next: LinkedList<T>)
}
 
let list = LinkedList.node(value: 1, next: .node(value: 2, next: .empty))
  • 协议(Protocol)扩展:枚举可以遵循协议并实现协议中的方法和属性。
    示例:
protocol Printable {
    var description: String { get }
}
 
enum Weekday: Printable {
    case monday, tuesday, wednesday, thursday, friday, saturday, sunday
    
    var description: String {
        switch self {
        case .monday:
            return "Monday"
        case .tuesday:
            return "Tuesday"
        case .wednesday:
            return "Wednesday"
        case .thursday:
            return "Thursday"
        case .friday:
            return "Friday"
        case .saturday:
            return "Saturday"
        case .sunday:
            return "Sunday"
        }
    }
}
 
let today = Weekday.monday
print(today.description) // 输出 "Monday"
  • enum中定义方法:
enum Wearable {
    enum Weight: Int {
        case Light = 1
    }
    enum Armor: Int {
        case Light = 2
    }
    case Helmet(weight: Weight, armor: Armor)
        func attributes() -> (weight: Int, armor: Int) {
       switch self {
             case .Helmet(let w, let a): return (weight: w.rawValue * 2, armor: w.rawValue * 4)
       }
    }
}
let woodenHelmetProps = Wearable.Helmet(weight: .Light, armor: .Light).attributes()
print (woodenHelmetProps)
// prints "(2, 4)"
enum Device { 
    case iPad, iPhone, AppleTV, AppleWatch 
    func introduced() -> String {
       switch self {
         case AppleTV: return "\(self) was introduced 2006"
         case iPhone: return "\(self) was introduced 2007"
         case iPad: return "\(self) was introduced 2010"
         case AppleWatch: return "\(self) was introduced 2014"
       }
    }
}
print (Device.iPhone.introduced())
// prints: "iPhone was introduced 2007"
  • 枚举创建静态方法(static methods)
    你也能够为枚举创建一些静态方法(static methods)。换言之通过一个非枚举类型来创建一个枚举。在这个示例中,我们需要考虑用户有时将苹果设备叫错的情况(比如AppleWatch叫成iWatch),需要返回一个合适的名称。
enum Device { 
    case AppleWatch 
    static func fromSlang(term: String) -> Device? {
      if term == "iWatch" {
      return .AppleWatch
      }
      return nil
    }
}
print (Device.fromSlang("iWatch"))
  • 枚举创建可变方法(Mutating Methods)
    方法可以声明为mutating。这样就允许改变隐藏参数self的case值了。
enum TriStateSwitch {
    case Off, Low, High
    mutating func next() {
    switch self {
    case Off:
        self = Low
    case Low:
        self = High
    case High:
        self = Off
    }
    }
}
var ovenLight = TriStateSwitch.Low
ovenLight.next()
// ovenLight 现在等于.On
ovenLight.next()
// ovenLight 现在等于.Off
  • 属性(Properties)
    尽管增加一个存储属性到枚举中不被允许,但你依然能够创建计算属性。当然,计算属性的内容都是建立在枚举值下或者枚举关联值得到的。
enum Device {
  case iPad, iPhone
  var year: Int {
    switch self {
        case iPhone: return 2007
        case iPad: return 2010
     }
  }
}
  • 枚举泛型(Generic Enums)
    枚举也支持泛型参数定义。你可以使用它们以适应枚举中的关联值。就拿直接来自Swift标准库中的简单例子来说,即Optional类型。你主要可能通过以下几种方式使用它:可选链(optional chaining(?))、if-let可选绑定、guard let、或switch,但是从语法角度来说你也可以这么使用Optional:
// Simplified implementation of Swift's Optional
enum MyOptional<T> {
  case Some(T)
  case None
}

let aValue = Optional<Int>.Some(5)
let noValue = Optional<Int>.None
if noValue == Optional.None { print("No value") }
  • 使用自定义类型作为枚举的值 (使用NSColor / UIColor) 如果我们忽略关联值,则枚举的值就只能是整型,浮点型,字符串和布尔类型。如果想要支持别的类型,则可以通过实现 StringLiteralConvertible 协议来完成,这可以让我们通过对字符串的序列化和反序列化来使枚举支持自定义类型。
    我们需要为想要支持的自定义类型增加一个扩展,让其实现 StringLiteralConvertible 协议。这个协议要求我们实现三个构造方法,这三个方法都需要使用一个String类型的参数,并且我们需要将这个字符串转换成我们需要的类型。
    示例:
extension CGSize: StringLiteralConvertible {
    public init(stringLiteral value: String) {
    let size = CGSizeFromString(value)
    self.init(width: size.width, height: size.height)
    }

    public init(extendedGraphemeClusterLiteral value: String) {
    let size = CGSizeFromString(value)
    self.init(width: size.width, height: size.height)
    }

    public init(unicodeScalarLiteral value: String) {
    let size = CGSizeFromString(value)
    self.init(width: size.width, height: size.height)
    }
}

现在就可以来实现我们需要的枚举了,不过这里有一个缺点:初始化的值必须写成字符串形式,因为这就是我们定义的枚举需要接受的类型(记住,我们实现了 StringLiteralConvertible,因此String可以转化成CGSize类型)

enum Devices: CGSize {
   case iPhone3GS = "{320, 480}"
   case iPhone5 = "{320, 568}"
   case iPhone6 = "{375, 667}"
   case iPhone6Plus = "{414, 736}"
}

终于,我们可以使用 CGPoint 类型的枚举了。需要注意的是,当要获取真实的 CGPoint 的值的时候,我们需要访问枚举的是 rawValue 属性。

let a = Devices.iPhone5
let b = a.rawValue
print("the phone size string is \(a), width is \(b.width), height is \(b.height)")
// prints : the phone size string is iPhone5, width is 320.0, height is 568.0

参考:
www.jianshu.com/p/11f5b818c…