枚举(三)

2018-06-04  本文已影响1人  小橘子成长记

关联值

关联值在表达能力上将枚举提升到更高层次。你可以给每个枚举案例关联自定义的相关值。

以下是关联值的特性:
1 每个枚举案例都有零个或多个关联值。
2 每个枚举的关联值都有自己的数据类型。
3 可以像定义命名函数参数那样,用定义关联值。

枚举可以具有原始值或关联值,但不能同时具有这两个值。

在上一个小练习中,你定义了一个硬币钱包。假设你把钱存到银行。然后你可以去自动取款机取钱:

var balance = 100
func withdraw(amount: Int) {
  balance -= amount
}

自动取款机永远不会让你取比你存的多的钱,所以它需要一种让你知道交易是否成功的方法。可以将其作为枚举的相关值来实现:

enum WithdrawalResult {
  case success(newBalance: Int)
  case error(message: String)
}

每种情况都有一个值。对于成功情况,关联的Int将保持新的平衡;对于错误情况,关联的字符串将具有某种类型的错误消息。

可以使枚举重写withdraw方法

func withdraw(amount: Int) -> WithdrawalResult {
 if amount <= balance {
   balance -= amount
   return .success(newBalance: balance)
 } else {
   return .error(message: "Not enough money!")
 }
}

现在你可以调用这个方法,处理返回的结果

let result = withdraw(amount: 99)
switch result {
case .success(let newBalance):
  print("Your new balance is: \(newBalance)")
case .error(let message):
  print(message)
}

注意如何使用let 绑定来读取相关联的值。关联值不是你可以自由访问的属性,所以你需要像这样的let绑定来读取它们。记住,绑定的常量newBalance和message在switch case下是局部的。它们不需要具有与关联值相同的名称,虽然这样做是常见的做法。

你可以在调试控制台中看到“你的新余额是:1”。

许多地方通过访问枚举中的关联值来发挥作用。例如,internet服务器经常使用枚举来区分请求的类型:

enum HTTPMethod {
  case get
  case post(body: String)
}

在银行帐户示例中,你希望在枚举中检查多个值。但只有一个符合要求,你可以在if case语句或guard case语句中使用模式匹配。他们是如何工作的:

let request = HTTPMethod.post(body: "Hi there")
guard case .post(let body) = request else {
  fatalError("No message was posted")
}
print(body)

在这段代码中,guard case检查request是否包含post枚举,如果包含,则读取并绑定关联值。

枚举作为状态模式

枚举是一个状态模式的例子,说明它一次只能是一个枚举值,不能是更多。交通灯很好地说明了这个概念:

enum TrafficLight {
 case red, yellow, green
}
let trafficLight = TrafficLight.red

一个交通灯永远不会同时是红色和绿色。你可以在其他现代设备中观察这种状态机行为,这些设备遵循预定的操作序列来响应事件序列。状态机的例子包括:

•自动售货机,当顾客放入足够的钱时,自动售货机就会售货
•下来之前,电梯会把乘客送到楼上。
•组合锁,要求组合号按正确顺序排列

要按预期操作,这些设备依赖于枚举的担保,即每次只能在一个状态。

case-less 枚举

在“方法”中,你学习了如何为一组相关类型方法创建名称空间。那一章的例子是这样的:

struct Math {
  static func factorial(of number: Int) -> Int {
    return (1...number).reduce(1, *)
  }
}
let factorial = Math.factorial(of: 6) // 720

有一件事你当时可能没有意识到,你可以创建一个数学实例,比如:

  let math = Math()

数学实例没有任何用途,因为它完全是空的;它没有任何存储属性。在这种情况下,更好的设计实际上是将Math从一个结构转换为一个枚举:

enum Math {
  static func factorial(of number: Int) -> Int {
    return (1...number).reduce(1, *)
  }
}

let factorial = Math.factorial(of: 6) // 720

如果你尝试做创建一个实例,编译器会给你一个错误:

let math = Math() // ERROR: No accessible initializers

没有case的枚举有时被称为无实例类型或底层类型。

正如在本章开头学到的,枚举是非常强大的。它们可以完成结构所能完成的大部分工作,包括自定义初始化器、计算属性和方法。为了创建枚举的实例,您必须将成员值指定为状态。如果没有成员值,则无法创建实例。

那么Math就无法被实例化,也没什么可实例化的,只有一个方法。这样就可以预防以后的程序员不小心实例化这个类。所以如果你想要创建一个没有值的实例,就可以选择case-less 枚举。

可选值

既然你已经学习了枚举,现在是时候让你知道一个小秘密了。有一个swift语言功能一直在你眼皮底下使用枚举:optional!在本节中,你将探索它们的底层机制。

选项就像容器一样,里面要么有东西,要么什么都没有:

var age: Int?
age = 17
age = nil

optional实际上是列举了两种情况:

  1. .none 没有就意味着没有价值。
  2. .some 表示存在一个值,该值作为关联值附加到枚举案例中。

您可以使用switch语句从optional中提取相关值:

switch age {
case .none:
  print("No value")
case .some(let value):
  print("Got a value: \(value)")
}

你可以在调试控制台中看到输出的“No value”消息。

尽管optionals确实是在后台的枚举,但是Swift隐藏了实现的细节,比如可选绑定,?和!操作符和诸如nil的关键字。

let optionalNil: Int? = .none
optionalNil == nil    // true
optionalNil == .none  // true

如果你在playground运行,你会看到nil和.none是相等的。

在“泛型”章节中,你将了解更多关于可选值optional的底层机制,包括如何编写可选值代码。

要点

•枚举是互斥情况的公共类型列表。
•枚举提供了一种类型安全的替代方法,可以替代老式的整数值。
•你可以使用枚举来处理响应、存储状态和封装值。
•可以使用case-less枚举作为名称空间并防止实例的创建。

上一篇 下一篇

猜你喜欢

热点阅读