引言
继续学习Swift文档,从上一章节:闭包,我们学习了Swift闭包相关的内容,如闭包的定义和使用、闭包的简写、速记参数名称、尾随闭包、捕获值、闭包的类型、转义闭包(@escaping)和自动闭包(@autoclosure)等这些内容。现在,我们学习Swift的枚举相关的内容。由于篇幅较长,这里分篇来记录,接下来,Fighting!
如果你已经熟练掌握了枚举的使用,那请移步下一章节:结构体和类
枚举
枚举为一组相关值定义了通用类型,并使您能够在代码中以类型安全的方式使用这些值。
如果熟悉C,就知道C中的枚举将相关名称分配给一组整数值。 Swift中的枚举更加灵活,无需为每种枚举提供值。 如果为每种枚举情况提供了一个值(称为原始值),则该值可以是字符串,字符或任何整数或浮点类型的值。
或者,枚举案例可以指定要存储的任何类型的关联值以及每个不同的案例值,这与其他语言中的并集或变体很相似。 您可以将一组常见的相关案例定义为一个枚举的一部分,每个案例都有一组与之关联的不同类型的适当类型的值。
Swift中的枚举本身就是一流的类型。 它们采用了许多传统上仅由类支持的功能(意思是可以和类一样提供相关功能),例如提供枚举当前值的附加信息的计算属性,以及提供与枚举所表示的值相关的功能的实例方法。 枚举还可以定义初始值设定项以提供初始case值。 可以扩展以扩展其功能,使其超出其最初的实现; 并可以遵循协议以提供标准功能。
想知道更多的功能,请参阅Properties, Methods, Initialization, Extensions, 和 Protocols.
1 枚举语法
您可以使用enum关键字引入枚举,并将其整个定义放在一对大括号内:
enum SomeEnumeration {
// enumeration definition goes here
}
这是指南针的四个要点的示例:
enum CompassPoint {
case north
case south
case east
case west
}
枚举里定义的这些值(例如north, south, east, 和west) 是枚举里的cases。可以使用case关键字新增cases。
注意
与C和Objective-C等语言不同,Swift枚举案例默认情况下未设置整数值。 在上面的CompassPoint示例中,北,南,东和西并不隐式等于0、1、2和3。相反,不同的枚举情况本身就是具有明确定义的CompassPoint类型的值。
多个cases可以写在一行上,用逗号隔开:
enum Planet {
case mercury, venus, earth, mars, jupiter, saturn, uranus, neptune
}
每个枚举定义都定义一个新类型。 与Swift中的其他类型一样,它们的名称(例如CompassPoint和Planet)以大写字母开头。 给枚举类型使用单数而不是复数的名称,以方便阅读:
var directionToHead = CompassPoint.west
当使用CompassPoint的可能值之一进行初始化时,会推断directionToHead的类型。 将directionToHead声明为CompassPoint后,您可以使用较短的点语法将其设置为其他CompassPoint值:
directionToHead = .east
directionToHead的类型是已知的,因此可以在设置其值时将类型删除。 当使用显式类型的枚举值时,这使得代码易于阅读。
2 使用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"
上面代码可以解读为:
判断directionToHead的值,如果directionToHead等于.north,则打印"Lots of planets have a north"...以此类推。
如控制流中所述,考虑枚举的情况时,switch语句必须是详尽的。 如果省略.west的case,则此代码不会编译,因为它没有考虑CompassPoint case的完整列表。 要求详尽无遗,以确保不会意外省略枚举case。
如果不适合为每个枚举案例提供case,则可以提供默认case以涵盖所有未明确解决的案例:
let somePlanet = Planet.earth
switch somePlanet {
case .earth:
print("Mostly harmless")
default:
print("Not a safe place for humans")
}
// Prints "Mostly harmless"
3 遍历枚举cases
对于某些枚举,收集所有该枚举的cases很有用。 您可以通过在枚举名称后输入:CaseIterable来启用此功能。 Swift将所有案例的集合作为枚举类型的allCases属性公开。 这是一个例子:
enum Beverage: CaseIterable {
case coffee, tea, juice
}
let numberOfChoices = Beverage.allCases.count
print("\(numberOfChoices) beverages available")
// Prints "3 beverages available"
在上面的示例中,您编写Beverage.allCases来访问包含所有Beverage枚举案例的集合。 您可以像使用其他集合一样使用allCases-该集合的元素是枚举类型的实例,因此在这种情况下,它们是“Beverage”值。 上面的示例计算了有多少个case,下面的示例使用for循环遍历所有案例。
for beverage in Beverage.allCases {
print(beverage)
}
// coffee
// tea
// juice
上面示例中使用的语法将枚举标记为遵循CaseIterable协议。 有关协议的信息,请参见Protocols。
4 关联值
上一节中的示例说明枚举的cases本身就是已定义(和键入)的值。 您可以将常量或变量设置为Planet.earth,然后再检查此值。 但是,有时可以将其他类型的值与这些案例值一起存储。 此附加信息称为关联值,每次将这种情况用作代码中的值时,它都会有所不同。
您可以定义Swift枚举来存储任何给定类型的关联值,并且如果需要,每种枚举的值类型可以不同。 类似于这些的枚举在其他编程语言中被称为区分联合,标记联合或变体。
例如,假设库存跟踪系统需要通过两种不同类型的条形码来跟踪产品。 某些产品用UPC格式的一维条形码标记,其使用数字0到9。每个条形码都有一个数字系统数字,后跟五个制造商代码数字和五个产品代码数字。 这些后面跟一个校验位,以验证代码是否已正确扫描:
其他产品带有QR码格式的2D条形码标签,可以使用任何ISO 8859-1字符,并且可以编码长达2,953个字符的字符串:
库存跟踪系统可以方便地将UPC条形码存储为四个整数的元组,并将QR码条形码存储为任意长度的字符串。
在Swift中,定义两种类型产品条形码的枚举可能看起来像这样:
enum Barcode {
case upc(Int, Int, Int, Int)
case qrCode(String)
}
可以被解读为:
定义了一个叫Barcode的枚举,带有一个(Int, Int, Int, Int)类型的upc值和一个String类型的qrCode值。
此定义不提供任何实际的Int或String值,只是定义Barcode常数和变量等于Barcode.upc或Barcode.qrCode时可以存储的关联值的类型。
然后,您可以使用以下两种类型之一创建新的条形码:
var productBarcode = Barcode.upc(8, 85909, 51226, 3)
本示例创建一个名为productBarcode的新变量,并为其分配Barcode.upc值,并具有关联的元组值(8,85909,51226,3)。
您可以为同一产品分配不同类型的条形码:
productBarcode = .qrCode("ABCDEFGHIJKLMNOP")
此时,原始的Barcode.upc及其整数值将替换为新的Barcode.qrCode及其字符串值。 Barcode类型的常量和变量可以存储.upc或.qrCode(及其关联值),但在任何给定时间只能存储其中之一。
您可以使用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."
5 原始值
关联值中的条形码示例显示了如何声明枚举的case存储了不同类型的关联值。 作为关联值的替代方法,枚举案例可以预先填充默认值(称为原始值),这些默认值都是相同的类型。
这是一个将原始ASCII值与命名枚举cases一起存储的示例:
enum ASCIIControlCharacter: Character {
case tab = "\t"
case lineFeed = "\n"
case carriageReturn = "\r"
}
在这里,被称为ASCIIControlCharacter的枚举的原始值被定义为Character类型,并被设置为一些更常见的ASCII控制字符。 字符串和字符中描述了字符值。
原始值可以是字符串,字符或任何整数或浮点数类型。 每个原始值在其枚举声明中必须唯一。
注意
原始值与关联值不同。 首次在代码中定义枚举时,原始值将设置为预填充的值,例如上面的三个ASCII代码。 特定枚举情况的原始值始终相同。 关联值是在您根据枚举的一种情况创建新的常数或变量时设置的,并且每次都可以不同。
5.1 隐式设置原始值
当您使用存储整数或字符串原始值的枚举时,不必为每种情况显式分配原始值。 不需要时,Swift会自动为您分配值。
例如,当整数用于原始值时,每种情况的隐式值都比前一种情况大一。 如果第一种情况未设置值,则其值为0。
下面的枚举是对先前Planet枚举的改进,其中整数原始值代表从太阳到每个行星的阶数:
enum Planet: Int {
case mercury = 1, venus, earth, mars, jupiter, saturn, uranus, neptune
}
在上面的示例中,Planet.mercury的显式原始值为1,Planet.venus的隐式原始值为2,依此类推。
如果将字符串用于原始值,则每种情况的隐式值就是该case名称的文本。
下面的枚举是对早期CompassPoint枚举的改进,使用字符串原始值来表示每个方向的名称:
enum CompassPoint: String {
case north, south, east, west
}
在上面的示例中,CompassPoint.south的隐式原始值为“ south”,依此类推。
您可以使用其rawValue属性访问枚举用例的原始值:
let earthsOrder = Planet.earth.rawValue
// earthsOrder is 3
let sunsetDirection = CompassPoint.west.rawValue
// sunsetDirection is "west"
5.2 用原始值初始化
如果您使用原始值类型定义枚举,则枚举会自动接收一个采用原始值类型值的初始化程序(作为称为rawValue的参数),并返回枚举case或nil。 您可以使用此初始化程序尝试创建枚举的新实例。
本示例从其原始值7识别天王星:
let possiblePlanet = Planet(rawValue: 7)
// possiblePlanet is of type Planet? and equals Planet.uranus
但是,并非所有可能的Int值都能找到匹配的行星。 因此,原始值初始化程序始终返回可选的枚举情况。 在上面的示例中,possiblePlanet的类型为Planet?或“可选的Planet”。
注意:
原始值初始化程序是一个失败的初始化程序,因为并非每个原始值都将返回枚举情况。 有关更多信息,请参见失败的初始化程序。
如果您尝试查找位置为11的行星,则原始值初始化程序返回的可选Planet值将为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的行星。如果让somePlanet = Planet(rawValue:11)创建,则该语句创建一个可选Planet,并将somePlanet设置为该可选Planet的值(如果可以检索)。 。 在这种情况下,无法检索位置为11的行星,因此执行else分支。
递归枚举
递归枚举是一种枚举,该枚举具有该枚举的另一个实例作为一个或多个枚举案例的关联值。 您可以通过在其前面写一个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创建的ArithmeticExpression递归枚举:
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"
该函数通过简单地返回相关值来表示纯数字。 它通过声明left和right表达式表示addition或multiplication,然后将它们相加或相乘来表示加法或乘法。
总结
这一章节内容主要的知识点有:
- 枚举语法:用enum关键字声明。与C和OC不一样的是,case的类型不仅仅只能设置为整数值,还可以是其他类型。
- Swift里的多个case可以写在一行上,用逗号隔开。
- 可以用switch匹配枚举的值。
- 在枚举名称后面加上:CaseIterable属性,就可以收集枚举里所有的cases,可用for-in循环遍历所有case。
- 可以定义Swift枚举来存储任何给定类型的关联值,并且如果需要,每种枚举的值类型可以不同,如:
enum Barcode {
case upc(Int, Int, Int, Int)
case qrCode(String)
}
- 枚举case可以预先填充默认值(称为原始值),这些默认值都是相同的类型,如:
enum ASCIIControlCharacter: Character {
case tab = "\t"
case lineFeed = "\n"
case carriageReturn = "\r"
}
可以隐式设置原始值:
enum Planet: Int {
case mercury = 1, venus, earth, mars, jupiter, saturn, uranus, neptune
}
则Planet.mercury的显式原始值为1,Planet.venus的隐式原始值为2,以此类推。
如果将字符串用于原始值,则每种情况的隐式值就是该case名称的文本。
enum CompassPoint: String {
case north, south, east, west
}
可以使用其rawValue属性访问枚举用例的原始值:
let earthsOrder = Planet.earth.rawValue
// earthsOrder is 3
let sunsetDirection = CompassPoint.west.rawValue
// sunsetDirection is "west"
也可以用原始值进行初始化:
let possiblePlanet = Planet(rawValue: 7)
// possiblePlanet is of type Planet? and equals Planet.uranus
- 递归枚举是一种枚举,该枚举具有该枚举的另一个实例作为一个或多个枚举案例的关联值;用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)
}
好了,关于枚举的内容已经全部总结完毕,与OC里的枚举差别还是很大的,用法更灵活,只要掌握了枚举,就可以在项目开发中灵活运用,相信你对这个功能也很感兴趣。
最后,喜欢的朋友可以点个👍哦,你的鼓励才是我的动力,嘿嘿嘿~
参考文档:Swift - Enumerations