枚举定义了一个通用类型的一组相关的值,使你可以在你的代码中以一个安全的方式来使用这些值。
如果你熟悉 C 语言,你就会知道,在 C 语言中枚举指定相关名称为一组整型值。Swift 中的枚举更加灵活,不必给每一个枚举成员(enumeration member)提供一个值。如果一个值(被认为是“原始”值)被提供给每个枚举成员,则该值可以是一个字符串,一个字符,或是一个整型值或浮点值。
此外,枚举成员可以指定任何类型的关联值存储到枚举成员值中,就像其他语言中的联合体(unions)和变体(variants)。你可以定义一组通用的相关成员作为枚举的一部分,每一组都有不同的一组与它相关的适当类型的数值。
在 Swift 中,枚举类型是一等(first-class)类型。它们采用了很多传统上只被类所支持的特征,例如计算型属性(computed properties),用于提供关于枚举当前值的附加信息,实例方法(instance methods),用于提供和枚举所代表的值相关联的功能。枚举也可以定义构造器(initializers)来提供一个初始成员值;可以在原始的实现基础上扩展它们的功能;可以遵守协议(protocols)来提供标准的功能。
欲了解更多相关功能,请参考属性,方法,构造过程,扩展,和协议。
一、枚举语法
使用enum关键词并且把它们的整个定义放在一对大括号内:
enum SomeEnumeration{
//enumeration definition goes here
}
enum CompassPoint{
case North
case South
case East
case West
}
一个枚举中被定义的值(例如 North,South,East和West)是枚举的成员值(或者成员)。case关键词表明新的一行成员值将被定义。
注意: 不像 C 和 Objective-C 一样,Swift 的枚举成员在被创建时不会被赋予一个默认的整数值。在上面的CompassPoints例子中,North,South,East和West不是隐式的等于0,1,2和3。相反的,这些不同的枚举成员在CompassPoint的一种显式定义中拥有各自不同的值。
多个成员值可以出现在同一行上,用逗号隔开:
enum Planet{
case Mercury,Venus,Earth,Mars,Jupiter,Saturn,Uranus,Neptune
}
每个枚举定义了一个全新的类型。像 Swift 中其他类型一样,它们的名字(例如CompassPoint和Planet)应该以一个大写字母开头。给枚举类型起一个单数名字而不是复数名字,以便于读起来更加容易理解:
var directionToHead = CompassPoint.West
directionToHead的类型被推断当它被CompassPoint的一个可能值初始化。一旦directionToHead被声明为一个CompassPoint,你可以使用更短的点(.)语法将其设置为另一个CompassPoint的值:
directionToHead = .East
directionToHead的类型已知时,当设定它的值时,你可以不再写类型名。使用显式类型的枚举值可以让代码具有更好的可读性。(个人觉得这种点语法并没有完整的 CompassPoint.West易读性高,而且点语法在编译器中不能很好的自动提示, CompassPoint.West这种写法不仅有自动提示功能,也更清楚自己在获取哪个枚举的成员,不容易出错。)
二、在switch语句中匹配枚举值
可以在switch语法中像这样匹配枚举值:
directionToHead = .South
switch directionToHead{
case .North:
println("Lots of planets have a north")
case .South:
println("Watch out for penguins")
case .East:
println("Where the sun rises")
case .West:
println("Where the skies are blue")
}
在控制流中讲过,switch语句必须要保证其完整性。如果我们忽略了.West情况,就不会报错,因为它没有考虑到CompassPoint的全部成员。switch语句的完整性要求确保了枚举成员不会被意外遗漏。
当然,不需要匹配每个枚举成员的时候,可以使用default分支:
let somePlanet = Planet.Earth
switch somePlanet{
case .Earth:
println("Mostly harmless")
default:
println("Not a salf place for humans")
}
三、关联值 Associated Values
上面的例子中展示了枚举成员是如何定义的,你可以为Planet.Earth设置一个常量或者变量,之后检查这个值。然后,有时候如果可以把其他类型的值跟枚举成员绑定就非常有用了。这可以让你在枚举成员中存储额外的一些通用信息,并且当每次你在代码中利用该成员时允许这个信息产生变化。
你可以定义 Swift 的枚举存储任何类型的关联值,如果需要的话,每个成员的数据类型可以是各不相同的。枚举的这种特性跟其他语言中的可辨识联合(discriminated unions),标签联合(tagged unions),或者变体(variants)相似。
例如,假设一个库存跟踪系统需要利用两种不同类型的条形码来跟踪商品。有些商品上标有 UPC-A 格式的一维码,它使用数字0到9.每一个条形码都有一个代表“数字系统”的数字,该数字后接10个代表“标识符”的数字。最后一个数字是“检查”位,用来验证代码是否被正确扫描。
其他商品上标有 QR 码格式的二维码,它可以使用任何 ISO8859-1 字符,并且可以编码一个最多拥有2,953字符的字符串。
对于库存跟踪系统来说,能够把 UPC-A 码作为三个整型值的元组,和把 QR 码作为一个任何长度的字符串存储起来是方便的。
在 Swift 中,用来定义两种商品条码的枚举是这样子的:
enum BarCode{
case UPCA(Int,Int,Int,Int)
case QRCode(String)
}
以上代码可以这么理解:
“定义一个名为Barcode的枚举类型,它可以是UPCA的一个关联值(Int,Int,Int),或者QRCode的一个字符串类型(String)关联值。”
这个定义不提供任何Int或String的实际值,它只是定义了,当Barcode常量和变量等于Barcode.UPCA或Barcode.QRCode时,关联值的类型。
然后可以使用任何一种条码类型创建新的条码,如:
var productBarCode = BarCode.UPCA(8, 8909, 51226, 3)
同一个商品可以被分配给一个不同类型的条形码,如:
productBarCode = .QRCode("ADSDFSFSDF")
这时,原始的Barcode.UPCA和其整数值被新的Barcode.QRCode和其字符串值所替代。条形码的常量和变量可以存储一个.UPCA或者一个.QRCode(连同它的关联值),但是在任何指定时间只能存储其中之一。
像以前那样,不同的条形码类型可以使用一个switch语句来检查,然而这次关联值可以被提取作为switch语句的一部分。你可以在switch的case分支代码中提取每个关联值作为一个常量(用let前缀)或者作为一个变量(用var前缀)来使用:
switch productBarCode{
case .UPCA(let numberSystem,let manufacturer,let product,let check):
println("UPC-A:\(numberSystem),\(manufacturer),\(product),\(check)")
case .QRCode(let productCode):
println("QR code:\(productCode)")
}
如果一个枚举成员的所有关联值被提取为常量,或者它们全部被提取为变量,为了简洁,你可以只放置一个var或者let标注在成员名称前:
switch productBarCode{
case let .UPCA( numberSystem, manufacturer, product, check):
println("UPC-A:\(numberSystem),\(manufacturer),\(product),\(check)")
case let .QRCode( productCode):
println("QR code:\(productCode)")
}
四、原始值 Raw Values
作为关联值的替代,枚举成员可以被默认值(称为原始值)预先填充,其中这些原始值具有相同的类型。
这里是一个枚举成员存储原始 ASCII 值的例子:
enum ASCIIControlCharacter:Character{
case Tab = "\t"
case LineFeed = "\n"
case CarriageReturn = "\r"
}
在这里,称为ASCIIControlCharacter的枚举的原始值类型被定义为字符型Character,并被设置了一些比较常见的 ASCII 控制字符。
注意,原始值和关联值是不相同的。当你开始在你的代码中定义枚举的时候原始值是被预先填充的值,像上述三个 ASCII 码。对于一个特定的枚举成员,它的原始值始终是相同的。关联值是当你在创建一个基于枚举成员的新常量或变量时才会被设置,并且每次当你这么做的时候,它的值可以是不同的。
原始值可以是字符串,字符,或者任何整型值或浮点型值。每个原始值在它的枚举声明中必须是唯一的。当整型值被用于原始值,如果其他枚举成员没有值时,它们会自动递增。
下面的枚举是对之前Planet这个枚举的一个细化,利用原始整型值来表示每个 planet 在太阳系中的顺序:
enum Planet:Int{
case Mercury = 1,Venus,Earth,Mars,Jupiter,Saturn,Uranus,Neptune
}
自动递增意味着Planet.Venus的原始值是2,依次类推。
使用枚举成员的 rawValue 方法可以访问该枚举成员的原始值:
let earthsOrder = Planet.Earth.rawValue//3
1、通过一个原始值初始化枚举成员
使用枚举的rawValue方法来试图找到具有特定原始值的枚举成员。这个例子通过原始值7识别Uranus:
let possiblePlanet = Planet(rawValue: 7)
然而,并非所有可能的Int值都可以找到一个匹配的行星。正因为如此,rawValue方法可以返回一个可选的枚举成员。在上面的例子中,possiblePlanet是Planet?类型,或“可选的Planet”。
如果你试图寻找一个位置为9的行星,通过fromRaw返回的可选Planet值将是nil:
let positionToFind = 9
if let somePlanet = Planet(rawValue:positionToFind){
switch somePlanet{
case .Earth:
println("Mostly harmless")
default:
println("Not a safe place for humans")
}
}else{
println("There isn't a planet at position \(positionToFind)")
}
这个范例使用可选绑定(optional binding),通过原始值9试图访问一个行星。if let somePlanet = Planet(rawValue:9) 语句获得一个可选Planet,如果可选Planet可以被获得,把somePlanet设置成该可选Planet的内容。在这个范例中,无法检索到位置为9的行星,所以else分支被执行。
五、总结
本章讲解了Swift中的枚举类型,跟C中的枚举差别挺大的,不再像C中的枚举那样,只可以是整数了,Swift的枚举显得非常的灵活。尤其是Swift的枚举成员可以使用任何类型的值给它们赋值,这一特性让枚举变得非常的强大,而且易读性很高。注意一下几点:
1、 Swift 中的枚举更加灵活,不必给每一个枚举成员提供一个值。如果一个值(被认为是“原始”值)被提供给每个枚举成员,则该值可以是一个字符串,一个字符,或是一个整型值或浮点值。
2、 可以指定任何类型的关联值存储到枚举成员值中。
3、 Swift 的枚举成员在被创建时不会被赋予一个默认的整数值。
4、虽然枚举成员赋值的时候可以是任意值,但是枚举成员的原始值类型必须是一样的。
参考:
1.The Swift Programming Language