枚举为一组相关值定义了一个公共类型,并使您能够在代码中以类型安全的方式使用这些值。
如果您熟悉C,您将知道C枚举将相关名称分配给一组整数值。swift中的枚举更加灵活,不必为每个枚举案例提供值。如果为每个枚举事例提供了一个值(称为原始值),则该值可以是字符串、字符或任何整数或浮点类型的值。
或者,枚举事例可以指定要与每个不同事例值一起存储的任何类型的关联值,就像联合或变体在其他语言中所做的那样。您可以将一组常见的相关事例定义为一个枚举的一部分,每个枚举都有一组不同的值,这些值与相应的类型相关联。
swift中的枚举本身就是一流的类型。它们采用了许多传统上只由类支持的特性,例如计算属性提供有关枚举当前值的附加信息,实例方法提供与枚举表示的值相关的功能。枚举还可以定义初始值设定项以提供初始大小写值;可以扩展以扩展其功能,使其超出原始实现范围;并且可以符合协议以提供标准功能。
有关这些功能的更多信息,请阅读
Properties,
Methods,
Initialization,
Extensions,
Protocols.
Enumeration Syntax 枚举语法
使用enum关键字引入枚举,并将其整个定义放在一对大括号中:
enum SomeEnumeration {
// enumeration definition goes here
}
以下是指南针四个方向的示例:
enum CompassPoint {
case north
case south
case east
case west
}
枚举中定义的值(如北、南、东和西)是其枚举情况。使用case关键字引入新的枚举事例。
注意:与C和Objective-C等语言不同,Swift中枚举case默认情况下没有整数值。在上面的圆规点示例中,North、South、East和West不隐式等于0、1、2和3。相反,不同的枚举case本身就是值。
多个事例可以出现在一行上,用逗号分隔:
枚举行星{
水星、金星、地球、火星、木星、土星、天王星、海王星
}
enum Planet {
case mercury, venus, earth, mars, jupiter, saturn, uranus, neptune
}
每个枚举定义定义一个新类型。和swift其它类型一样,它们的名字(如CompassPoint和Planet)以大写字母开头。为枚举类型提供单数而非复数名称,以便它们理解为不言而喻的:
var directionToHead = CompassPoint.west
当使用指南针四个方向点的某个可能值初始化DirectionToHead时,将推断出它的类型。一旦DirectionToHead声明为指南针方向点,可以使用较短的点语法将其设置为不同的圆规点值:
directionToHead = .east
DirectionToHead的类型已知,因此可以在设置其值时删除该类型。这使得在使用显式类型的枚举值时代码可读性很高。
Matching Enumeration Values with a Switch Statement 用switch语句匹配枚举值
可以将单个枚举值与switch语句匹配:
directionToHead = .south
switch directionToHead {
case .north:
print("Lots of planets have a north")
case .south:
print("Watch out for penguins")
case .east:
print("Where the sun rises")
case .west:
print("Where the skies are blue")
}
// Prints "Watch out for penguins"
当不适合为每个枚举case提供案例时,可以提供一个默认案例来涵盖任何未明确解决的案例:
let somePlanet = Planet.earth
switch somePlanet {
case .earth:
print("Mostly harmless")
default:
print("Not a safe place for humans")
}
// Prints "Mostly harmless"
Iterating over Enumeration Cases 遍历枚举case
对于某些枚举,拥有该枚举的所有case的集合很有用。您可以通过在枚举的名称后写入 :CaseIterable 来启用此功能。Swift将所有case的集合作为枚举类型的AllCases属性公开。下面是一个例子:
enum Beverage: CaseIterable {
case coffee, tea, juice
}
let numberOfChoices = Beverage.allCases.count
print("\(numberOfChoices) beverages available")
// Prints "3 beverages available"
在上面的示例中,编写Beverage.allCases以访问包含饮料枚举的所有事例的集合。您可以像任何其他集合一样使用所有案例集合的元素都是枚举类型的实例,因此在本例中,它们是饮料值。上面的示例计算有多少个案例,下面的示例使用for循环迭代所有案例:
for beverage in Beverage.allCases {
print(beverage)
}
// coffee
// tea
// juice
上述示例中使用的语法将枚举标记为符合CaseIterable协议。有关协议的信息,请参阅Protocols。
Associated Values 关联值
上一节中的示例展示了枚举的情况是如何定义(和类型化)值的。您可以将一个常量或变量设置为Planet。,然后检查这个值。然而,有时能够将其他类型的值与这些case值一起存储是有用的。这个附加信息称为关联值,每次在代码中使用这个case作为值时,它都会发生变化。
您可以定义Swift枚举来存储任何给定类型的关联值,如果需要,值类型可以根据枚举的不同情况而有所不同。类似于这些的枚举称为区分的联合、标记的联合或其他编程语言中的变体。
例如,假设一个库存跟踪系统需要使用两种不同类型的条形码跟踪产品。有些产品用UPC格式的一维条形码标记,使用数字0到9。每个条形码都有一个数字系统数字,然后是五个制造商代码数字和五个产品代码数字。然后是一个校验数字,以确认代码已被正确扫描:
其他产品则采用二维码格式标注二维码,可以使用任何ISO 8859-1字符,最长可编码2953个字符:
库存跟踪系统可以方便地将UPC条形码存储为四个整数的元组,将QR码存储为任意长度的字符串。
在Swift中,定义这两种产品条形码的枚举可能是这样的:
enum Barcode {
case upc(Int, Int, Int, Int)
case qrCode(String)
}
这可以理解为:
定义一个称为条形码的枚举类型,它可以接受upc值与类型关联的值(Int、Int、Int、Int),也可以接受qrCode值与类型字符串关联的值。
这个定义没有提供任何实际的Int或String值——它只定义了与条形码常量和变量相等时可以存储的关联值的类型。upc或Barcode.qrCode。
然后,您可以使用以下两种类型创建新的条形码:
var productBarcode = Barcode.upc(8, 85909, 51226, 3)
本例创建了一个名为product条码的新变量,并为其赋值为Barcode.upc具有相关联的元组值(8,85909,51226,3)。
你可以为相同的产品指定不同类型的条码:
productBarcode = .qrCode("ABCDEFGHIJKLMNOP")
您可以使用switch语句检查不同的条形码类型,类似于使用switch语句匹配枚举值的示例。然而,这一次,相关值作为switch语句的一部分提取。您提取每个相关的值作为常量(带有let前缀)或变量(带有var前缀),以便在switch case的主体中使用:
switch productBarcode {
case .upc(let numberSystem, let manufacturer, let product, let check):
print("UPC: \(numberSystem), \(manufacturer), \(product), \(check).")
case .qrCode(let productCode):
print("QR code: \(productCode).")
}
// Prints "QR code: ABCDEFGHIJKLMNOP."
如果枚举用例的所有关联值都提取为常量,或者如果所有值都提取为变量,为简便起见,可以在用例名称前放置一个var或let注释:
switch productBarcode {
case let .upc(numberSystem, manufacturer, product, check):
print("UPC : \(numberSystem), \(manufacturer), \(product), \(check).")
case let .qrCode(productCode):
print("QR code: \(productCode).")
}
// Prints "QR code: ABCDEFGHIJKLMNOP."
Raw Values 原始值
关联值中的条形码示例显示了枚举的用例如何声明它们存储不同类型的关联值。作为关联值的替代方法,枚举用例可以预先填充默认值(称为原始值),这些值都具有相同的类型。
下面是一个示例,它将原始ASCII值与命名枚举用例一起存储:
enum ASCIIControlCharacter: Character {
case tab = "\t"
case lineFeed = "\n"
case carriageReturn = "\r"
}
在这里,名为ASCIIControlCharacter的枚举的原始值被定义为字符类型,并被设置为一些更常见的ASCII控制字符。
原始值可以是字符串、字符或任何整数或浮点数类型。每个原始值在其枚举声明中必须是唯一的。
注意:原始值与关联值不相同。当您第一次在代码中定义枚举时,原始值被设置为预填充值,就像上面的三个ASCII代码一样。特定枚举用例的原始值总是相同的。关联值是在根据枚举的一种情况创建新常量或变量时设置的,每次创建时都可能不同。
Implicitly Assigned Raw Values 隐式赋值原始值
当处理存储整数或字符串原始值的枚举时,不必为每种情况显式分配原始值。如果没有,Swift会自动为您分配值。
例如,当将整数用于原始值时,每种情况的隐式值都比前一种情况多一个。如果第一种情况没有值集,它的值是0。
下面的枚举是对早期行星枚举的改进,使用整数原始值表示每个行星来自太阳的顺序:
enum Planet: Int {
case mercury = 1, venus, earth, mars, jupiter, saturn, uranus, neptune
}
当字符串用于原始值时,每种情况的隐式值都是该情况名称的文本。
下面的枚举是前面的spoint枚举的改进,用字符串原始值表示每个方向的名称:
enum CompassPoint: String {
case north, south, east, west
}
在上面的例子中,是CompassPoint。south有一个隐含的原始值“south”,依此类推。
访问枚举用例的raw值及其rawValue属性:
let earthsOrder = Planet.earth.rawValue
// earthsOrder is 3
let sunsetDirection = CompassPoint.west.rawValue
// sunsetDirection is "west"
Initializing from a Raw Value 从原始值初始化
如果使用raw-value类型定义枚举,枚举将自动接收一个初始化器,该初始化器接受原始值类型的值(作为一个名为rawValue的参数),并返回枚举用例或nil。可以使用此初始化器尝试创建枚举的新实例。
这个例子:从天王星的原始值7:
let possiblePlanet = Planet(rawValue: 7)
// possiblePlanet is of type Planet? and equals Planet.uranus
然而,并不是所有可能的Int值都能找到匹配的行星。因此,原始值初始化器总是返回一个可选枚举用例。在上面的例子中,可能行星是行星的类型?,或“可选行星”。
注意: 原始值初始化器是一个可失败的初始化器,因为不是每个原始值都会返回枚举用例。有关更多信息,请参见 Failable Initializers。
如果您试图找到位置为11的行星,原始值初始化器返回的可选行星值将为nil:
let positionToFind = 11
if let somePlanet = Planet(rawValue: positionToFind) {
switch somePlanet {
case .earth:
print("Mostly harmless")
default:
print("Not a safe place for humans")
}
} else {
print("There isn't a planet at position \(positionToFind)")
}
// Prints "There isn't a planet at position 11"
本例使用可选绑定尝试访问原始值为11的行星。语句if let somePlanet = Planet(rawValue: 11)创建一个可选的行星,并将somePlanet设置为该可选行星的值(如果可以检索该行星)。在本例中,不可能检索位置为11的行星,因此执行else分支。
Recursive Enumerations 递归枚举
类似递归函数,函数调用函数本身。递归枚举就是枚举在case中在调用枚举自身
递归枚举是一个枚举,该枚举的另一个实例作为一个或多个枚举用例的关联值。通过在枚举用例之前编写间接性 ( indirect 关键字 ) 来指示枚举用例是递归的,这告诉编译器插入必要的间接性层。
例如,下面是一个存储简单算术表达式的枚举:
enum ArithmeticExpression {
case number(Int)
indirect case addition(ArithmeticExpression, ArithmeticExpression)
indirect case multiplication(ArithmeticExpression, ArithmeticExpression)
}
您也可以在枚举开始前编写间接性,以便为枚举中所有具有关联值的情况启用间接性:
indirect enum ArithmeticExpression {
case number(Int)
case addition(ArithmeticExpression, ArithmeticExpression)
case multiplication(ArithmeticExpression, ArithmeticExpression)
}
此枚举可以存储三种算术表达式:素数、两个表达式的加法和两个表达式的乘法。加法和乘法用例的关联值也是算术表达式——这些关联值使嵌套表达式成为可能。例如,表达式(5 + 4)* 2在乘法的右边有一个数字,在乘法的左边有另一个表达式。由于数据是嵌套的,用于存储数据的枚举也需要支持嵌套——这意味着枚举需要递归。下面的代码是为(5 + 4)* 2创建的算术表达式递归枚举:
let five = ArithmeticExpression.number(5)
let four = ArithmeticExpression.number(4)
let sum = ArithmeticExpression.addition(five, four)
let product = ArithmeticExpression.multiplication(sum, ArithmeticExpression.number(2))
递归函数是处理具有递归结构的数据的简单方法。例如,这是一个计算算术表达式的函数:
func evaluate(_ expression: ArithmeticExpression) -> Int {
switch expression {
case let .number(value):
return value
case let .addition(left, right):
return evaluate(left) + evaluate(right)
case let .multiplication(left, right):
return evaluate(left) * evaluate(right)
}
}
print(evaluate(product))
// Prints "18"
这个函数通过简单地返回相关联的值来计算一个素数。它通过计算左边的表达式来计算加法或乘法,计算右边的表达式,然后相加或相乘。