枚举(三)
关联值
关联值在表达能力上将枚举提升到更高层次。你可以给每个枚举案例关联自定义的相关值。
以下是关联值的特性:
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实际上是列举了两种情况:
- .none 没有就意味着没有价值。
- .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枚举作为名称空间并防止实例的创建。