下列()标志符属性布尔值类型有什么属性属性(即可只需要指定属性存在,而不用指其值得标志符属

本文是一篇详细且具有实战意义嘚教程涵盖几乎所有枚举(Enum)知识点,为你解答Swift中枚举的应用场合以及使用方法

和类似,Swift中的枚举乍看之下更像是C语言中枚举的进阶版本即允许你定义一种类型,用于表示普通事情中某种用例不过深入挖掘之后,凭借Swift背后特别的设计理念相比较C语言枚举来说其在实际場景中的应用更为广泛。特别是作为强大的工具Swift中的枚举能够清晰表达代码的意图。

本文中我们将首先了解基础语法和使用枚举的可能性,接着通过实战教你如何以及何时使用枚举最后我们还会大致了解下Swift标准库中枚举是如何被使用的。

正式开始学习之前先给出枚舉的定义。之后我们将回过头再来讨论它

枚举声明的类型是囊括可能状态的有限集,且可以具有附加值通过内嵌(nesting),方法(method),关联值(associated values)和模式匹配(pattern matching),枚举可以分层次地定义任何有组织的数据。

简要概述如何定义和使用枚举

试想我们正在开发一款游戏,玩家能够朝四个方向移动所鉯喽,玩家的运动轨迹受到了限制显然,我们能够使用枚举来表述这一情况:

紧接着你可以使用获取到Movement的枚举值,或者按照特定情况执荇操作:

案例中我们无须明确指出enum的实际名称(即case Move.Left:print("Left"))。因为类型检查器能够自动为此进行类型推算这对于那些UIKit以及AppKit中错综复杂的枚举是灰常囿用的。

当然你可能想要为enum中每个case分配一个值。这相当有用比如枚举自身实际与某事或某物挂钩时,往往这些东西又需要使用不同类型来表述在C语言中,你只能为枚举case分配整型值而Swift则提供了更多的灵活性。


 
对于
String和Int类型来说你甚至可以忽略为枚举中的case赋值,Swift编译器吔能正常工作

 
Swift枚举中支持以下四种关联值类型:

 
不过某种情形下,你可能想要通过一个已有的raw value来创建一个enum case这种情况下,枚举提供了一个指定构造方法:
倘若使用rawValue构造器切记它是一个可失败构造器()。换言之构造方法返回值为,因为有时候传入的值可能与任意一个case都不匹配。仳如Movement(rawValue:42)
如果你想要以底层 C 二进制编码形式呈现某物或某事,使得更具可读性这是一个非常有用的功能。例如可以看一下中的VNode Flags标志位的編码方式:
如此便可以使你的DeleteWrite用例声明一目了然,稍后一旦需要只需将raw value传入 C 函数中即可。
 
如果你有特定子类型的需求可以对enum进行嵌套。这样就允许你为实际的enum中包含其他明确信息的enumRPG游戏中的每个角色为例,每个角色能够拥有武器因此所有角色都可以获取同一个武器集合。而游戏中的其他实例则无法获取这些武器(比如食人魔,它们仅使用棍棒)
现在,你可以通过层级结构来描述角色允许访问的项目条
 
同样地,你也能够在structsclasses中内嵌枚举接着上面的例子:
同样地,这也将有助于我们将相关的信息集中在一个位置
 
关联值是将额外信息附加到enum case中的一种极好的方式。打个比方你正在开发一款交易引擎,可能存在两种不同的交易类型除此之外每手交易还要制定明确嘚股票名称和交易数量:
 
然而股票的价值和数量显然从属于交易,让他们作为独立的参数显得模棱两可你可能已经想到要往struct中内嵌一个枚舉了,不过关联值提供了一种更清爽的解决方案:
 
如果你想要访问这些值再次救场:
 
关联值不需要附加标签的声明:
倘若你添加了,那么烸当创建枚举用例时,你都需要将这些标签标示出来
 
更重要的是,Swift内部相关信息其实是一个元组,所以你可以像下面这样做:
 
因此你无法为枚舉分配诸如CGPoint类型的值。
倘若你想要读取枚举的值可以通过rawValue属性来实现:

语法允许您将元组当作一个简单的数据结构,稍后元组将自动转换到高级类型,就比如enum case想象一个应用程序可以让用户来配置电脑:

配置的每个步骤均通过递交元组到enum中进行内容更新。倘若我们从函数式编程Φ获得启发这将变得更好。

最后我们可以将不同配置步骤串联起来。这在配置步骤繁多的情况下相当有用

关联值可以以多种方式使鼡。常言道:一码胜千言, 下面就上几段简单的示例代码这几段代码没有特定的顺序。


 
 
你也可以在enum中像这样定义方法:

枚举中的方法为每一個enum case而“生”所以倘若想要在特定情况执行特定代码的话,你需要分支处理或采用switch语句来明确正确的代码路径

尽管增加一个存储属性到枚举中不被允许,但你依然能够创建计算属性当然,计算属性的内容都是建立在枚举值下或者枚举关联值得到的

你也能够为枚举创建┅些静态方法(static methods)。换言之通过一个非枚举类型来创建一个枚举在这个示例中,我们需要考虑用户有时将苹果设备叫错的情况(比如AppleWatch叫成iWatch),需要返回一个合适的名称

方法可以声明为mutating。这样就允许改变隐藏参数selfcase值了

至此,我们已经大致了解了Swift中枚举语法的基本用例在开始迈姠进阶之路之前,让我们重新审视文章开篇给出的定义看看现在是否变得更清晰了。

枚举声明的类型是囊括可能状态的有限集且可以具有附加值。通过内嵌(nesting),方法(method),关联值(associated values)和模式匹配(pattern matching),枚举可以分层次地定义任何有组织的数据

现在我们已经对这个定义更加清晰了。确实如果我们添加关联值和嵌套,enum就看起来就像一个封闭的、简化的struct相比较struct,前者优势体现在能够为分类与层次结构编码


 
方法和静态方法的添加允许我们为
enum附加功能,这意味着无须依靠额外函数就能实现

 
 
一些协议的实现可能需要根据内部状态来相应处理要求。例如定义一个管理银行账号的协议
你也许会简单地拿struct实现这个协议,但是考虑应用的上下文enum是一个更明智的处理方法。不过你无法添加一个存储属性到enum中就像var remainingFuns:Int。那么你会如何构造呢***灰常简单,你可以使用关联值完美解决:
为了保持代码清爽我们可以在enum的协议扩展(protocl extension)中定义必须嘚协议函数:
 
 

Swift协议定义一个接口或类型以供其他数据结构来遵循。enum当然也不例外我们先从Swift标准库中的一个例子开始.

该协议只有一个要求,即一个只读(getter)类型的字符串(String类型)我们可以很容易为enum实现这个协议。

正如你所看见的我们通过将值存储到enum cases中实现了协议所有要求项。如此莋法还有一个妙不可言的地方:现在整个代码基础上你只需要一个模式匹配就能测试空账号输入的情况你不需要关心剩余资金是否等于零。

同时我们也在账号(Accout)中内嵌了一个遵循ErrorType协议的枚举,这样我们就可以使用Swift2.0语法来进行错误处理了这里给出更详细的教程。

正如刚才所見枚举也可以进行扩展。最明显的用例就是将枚举的casemethod分离这样阅读你的代码能够简单快速地消化掉enum内容,紧接着转移到方法定义:

现茬我们为enum扩展方法:

你同样可以通过写一个扩展来遵循一个特定的协议:

枚举也支持泛型参数定义。你可以使用它们以适应枚举中的关联值就拿直接来自Swift标准库中的简单例子来说,即Optional类型你主要可能通过以下几种方式使用它:可选链(optional chaining(?))、if-let可选绑定、guard let、或switch,但是从语法角度来说伱也可以这么使用Optional:

这是Optional最直接的用例并未使用任何语法糖,但是不可否认Swift中语法糖的加入使得你的工作更简单如果你观察上面的实例玳码,你恐怕已经猜到Optional内部实现是这样的:


 
这里有啥特别呢注意枚举的关联值采用泛型参数
T作为自身类型,这样可选类型构造任何你想要嘚返回值
枚举可以拥有多个泛型参数。就拿熟知的Either类为例它并非是Swift标准库中的一部分,而是实现于众多开源库以及
其他函数式编程语訁比如HaskellF#。设计想法是这样的:相比较仅仅返回一个值或没有值(née Optional)你更期望返回一个成功值或者一些反馈信息(比如错误值)。

 
最后
Swift中所囿在classstruct中奏效的类型约束,在enum中同样适用
 

对枚举的 case 进行迭代

 
一个特别经常被问到的问题就是如何对枚举中的 case 进行迭代。可惜的是枚举並没有遵守SequenceType协议,因此没有一个官方的做法来对其进行迭代取决于枚举的类型,对其进行迭代可能也简单也有可能很困难。在上有一個很好的讨论贴贴子里面讨论到的不同情况太多了,如果只在这里摘取一些会有片面性而如果将全部情况都列出来,则会太多
 

有一個名为,可以让规范我们以定义合适的方法如此一来,Swift 便可以正确地将枚举转成 Objective-C 类型但我猜这个协议被隐藏起来一定是有原因的。然洏从理论上来讲,这个协议还是允许我们将枚举(包括其实枚举值)正确地桥接到 Objective-C 当中
但是,我们并不一定非要使用上面提到的这个方法为枚举添加两个方法,使用 @objc 定义一个替代类型如此一来我们便可以自由地将枚举进行转换了,并且这种方式不需要遵守私有协议:
这個方法有一个的缺点我们需要将枚举映射为 Objective-C 中的 NSObject 基础类型(我们也可以直接使用 NSDictionary),但是当我们碰到一些确实需要在 Objective-C 当中获取有关联值的枚举时,这是一个可以使用的方法
 
Erica Sadun 写过一篇很流弊的,涉及到枚举底层的方方面面在生产代码中绝不应该使用到这些东西,但是学习┅下还是相当有趣的在这里,我准备只提到那篇博客中一条如果想了解更多,请移步到原文:

枚举通常都是一个字节长度[...]如果你真嘚很傻很天真,你当然可以定义一个有成百上千个 case 的枚举在这种情况下,取决于最少所需要的比特数枚举可能占据两个字节或者更多。

 

Swift 标准库中的枚举

 
在我们准备继续探索枚举在项目中的不同用例之前先看一下在 Swift 标准库当中是如何使用枚举可能会更诱人,所以现在让峩们先来看看


和 这两个枚举被用在 Swift 反射 API 的上下文当中。

这个枚举包含了当前进程的命令行参数(Process.argc, Process.arguments)这是一个相当有趣的枚举类型,因为在 Swift 1.0 當中它是被作为一个结构体来实现的。
 
我们已经在前面几个小节当中看过了许多有用的枚举类型包括 OptionalEither, FileNode 还有二叉树然而,还存在很哆场合使用枚举要胜过使用结构体和类。一般来讲如果问题可以被***为有限的不同类别,则使用枚举应该就是正确的选择即使只囿两种 case,这也是一个使用枚举的完美场景正如
以下列举了一些枚举类型在实战中的使用示例,可以用来点燃你的创造力
 
说到枚举的实踐使用,当然少不了在 Swift 2.0 当中新推出的错误处理标记为可抛出的函数可以抛出任何遵守了 ErrorType 空协议的类型。正如 Swift 官方文档中所写的:

Swift 的枚举特别适用于构建一组相关的错误状态可以通过关联值来为其增加额外的附加信息。

 
作为一个示例我们来看下流行的。当 JSON 解析失败的时候它有可能是以下两种主要原因:
  1. JSON 数据缺少某些最终模型所需要的键(比如你的模型有一个 username 的属性,但是 JSON 中缺少了)
 
除此之外Argo 还为不包含茬上述两个类别中的错误提供了自定义错误。它们的 ErrorType 枚举是类似这样的:
所有的 case 都有一个关联值用来包含关于错误的附加信息

这个 ErrorType 实现叻完整的 REST 程序栈解析有可能出现的错误,包含了所有在解析结构体与类时会出现的错误

更多关于ErrorType以及此种枚举类型的示例可以参看。
 
KVO即使不用这个标记,didSet语法也可以很容易地实现简单的观察模式在这里可以使用枚举,它可以使被观察者的变化更加清晰明了设想我们偠对一个集合进行观察。如果我们稍微思考一下就会发现这只有几种可能的情况:一个或多个项被插入一个或多个项被删除,一个或多個项被更新这听起来就是枚举可以完成的工作:
之后,观察对象就可以使用一个很简洁的方式来获取已经发生的事情的详细信息这也鈳以通过为其增加 oldValuenewValue 的简单方法来扩展它的功能。
 
如果我们正在使用一个外部系统而这个系统使用了状态码(或者错误码)来传递错误信息,类似 HTTP 状态码这种情况下枚举就是一种很明显并且很好的方式来对信息进行封装 。
 
枚举也经常被用于将 JSON 解析后的结果映射成 Swift 的原生类型这里有一个简短的例子:
类似地,如果我们解析了其它的东西也可以使用这种方式将解析结果转化我们 Swift 的类型。
 
枚举可以用来将字符串类型的重用标识或者 storyboard 标识映射为类型系统可以进行检查的类型假设我们有一个拥有很多原型 Cell 的 UITableView:
 
单位以及单位转换是另一个使用枚举嘚绝佳场合。可以将单位及其对应的转换率映射起来然后添加方法来对单位进行自动的转换。以下是一个相当简单的示例:
另一个示例昰货币的转换以及数学符号(比如角度与弧度)也可以从中受益。
 
游戏也是枚举中的另一个相当好的用例屏幕上的大多数实体都属于一个特定种族的类型(敌人,障碍纹理,...)相对于本地的 iOS 或者 Mac 应用,游戏更像是一个白板即开发游戏我们可以使用全新的对象以及全新的关聯创造一个全新的世界,而 iOS 或者 OSX 需要使用预定义的 UIButtonsUITableViews,UITableViewCells 或者 NSStackView.
不仅如此由于枚举可以遵守协议,我们可以利用协议扩展和基于协议的编程為不同为游戏定义的枚举增加功能这里是一个用来展示这种层级的的简短示例:
 
在一个稍微大一点的 Xcode 项目中,我们很快就会有一大堆通過字符串来访问的资源在前面的小节中,我们已经提过重用标识和 storyboard 的标识但是除了这两样,还存在很多资源:图像Segues,Nibs字体以及其咜资源。通常情况下这些资源都可以分成不同的集合。如果是这样的话一个类型化的字符串会是一个让编译器帮我们进行类型检查的恏方法。
对于 iOS 开发者这个第三方库可以为以上提到的情况自动生成结构体。但是有些时候你可能需要有更多的控制(或者你可能是一个Mac开發者)
 
Rest API 是枚举的绝佳用例。它们都是分组的它们都是有限的 API 集合,并且它们也可能会有附加的查询或者命名的参数而这可以使用关联徝来实现。

就是基本这个思想使用枚举对 rest 端点进行映射。
 
Airspeed Velocity有一篇说明了如何使用枚举来实现一个链表那篇文章中的大多数代码都超出叻枚举的知识,并涉及到了大量其它有趣的主题但是,链表最基本的定义是类似这样的(我对其进行了一些简化):
每一个节点(Node) case 都指向了下┅个 case 通过使用枚举而非其它类型,我们可以避免使用一个可选的 next 类型以用来表示链表的结束
Airspeed Velocity 还写过一篇超赞的博客,关于如何使用 Swift 的間接枚举类型来实现红黑树所以如果你已经阅读过关于链表的博客,你可能想继续阅读
 
这是 Erica Sadun 提出的。简单来讲就是任何我们需要用┅个属性的字典来对一个项进行设置的时候,都应该使用一系列有关联值的枚举来替代使用这方法,类型检查系统可以确保配置的值都昰正确的类型
 
与之前类似,我将会用一系列枚举的局限性来结束本篇文章
 
David Owens写过一篇,他觉得当前的关联值提取方式是很笨重的我墙裂推荐你去看一下他的原文,在这里我对它的要旨进行下说明:为了从一个枚举中获取关联值我们必须使用模式匹配。然而关联值就昰关联在特定枚举 case 的高效元组。而元组是可以使用更简单的方式来获取它内部值即 .keyword 或者 .0

 
如果你也同样觉得我们应该使用相同的方法来對枚举进行解构(deconstruct)这里有个 rdar: (译者注:一开始我不明白 rdar 是啥意思,后来我 google 了下如果你也有兴趣,也可以自己去搜索一下)

 
拥有关联值的枚举沒有遵守 equatable 协议这是一个遗憾,因为它为很多事情增加了不必要的复杂和麻烦深层的原因可能是因为关联值的底层使用是使用了元组,洏元组并没有遵守 equatable 协议然而,对于限定的 case 子集如果这些关联值的类型都遵守了 equatable 类型,我认为编译器应该默认为其生成 equatable 扩展

 
 
最大的问題就是对。我喜欢使用元组它们可以使很多事情变得更简单,但是他们目前还处于无文档状态并且在很多场合都无法使用在枚举当中,我们无法使用元组作为枚举的值:
这似乎看起来并不是一个最好的示例但是我们一旦开始使用枚举,就会经常陷入到需要用到类似上媔这个示例的情形中

迭代枚举的所有case

 
 
这个我们已经在前面讨论过了。目前还没有一个很好的方法来获得枚举中的所有 case 的集合以使我们可鉯对其进行迭代
 
另一个会碰到的事是枚举的关联值总是类型,但是我们却无法为这些类型指定默认值假设有这样一种情况:
我们依然可鉯使用不同的值创建新的 case,但是角色的默认设置依然会被映射

 
 
使用字符串序列化的形式,会让使用自定义类型的枚举比较困难然而在某些特定的情况下,这也会给我们增加不少便利(比较使用NSColor / UIColor的时候)不仅如此,我们完全可以对自己定义的类型使用这个方法

对枚举的关聯值进行比较

 

然而,一旦我们为枚举增加了关联值Swift 就没有办法正确地为两个枚举进行相等性判断,需要我们自己实现 == 运行符这并不是佷困难:
正如我们所见,我们通过 switch 语句对两个枚举的 case 进行判断并且只有当它们的 case 是匹配的时候(比如 Buy 和 Buy)才对它们的真实关联值进行判断。
 
静态方法 一节当中我们已经提到它们可以作为从不同数据构造枚举的方便形式在之前的例子里也展示过,对出版社经常误用的苹果设備名返回正确的名字:
我们也可以使用自定义构造方法来替换静态方法枚举与结构体和类的构造方法最大的不同在于,枚举的构造方法需要将隐式的 self 属性设置为正确的 case
在这个例子中,我们使用了可失败(failable)的构造方法但是,普通的构造方法也可以工作得很好:
 
间接类型是 Swift 2.0 噺增的一个类型 它们允许将枚举中一个 case 的关联值再次定义为枚举。举个例子假设我们想定义一个文件系统,用来表示文件以及包含文件的目录如果将文件目录定义为枚举的 case,则目录 case 的关联值应该再包含一个文件的数组作为它的关联值因为这是一个递归的操作,编譯器需要对此进行一个特殊的准备Swift 文档中是这么写的:

枚举和 case 可以被标记为间接的(indrect),这意味它们的关联值是被间接保存的这允许我们萣义递归的数据结构。

 
所以如果我们要定义 FileNode 的枚举,它应该会是这样的:
此处的 indrect 关键字告诉编译器间接地处理这个枚举的 case也可以对整個枚举类型使用这个关键字。:
这是一个很强大的特性可以让我们用非常简洁的方式来定义一个有着复杂关联的数据结构。

使用自定义类型作为枚举的值

 
如果我们忽略关联值则枚举的值就只能是整型,浮点型字符串和布尔类型。如果想要支持别的类型则可以通过实现 StringLiteralConvertible 協议来完成,这可以让我们通过对字符串的序列化和反序列化来使枚举支持自定义类型
作为一个例子,假设我们要定义一个枚举来保存鈈同的 iOS 设备的屏幕尺寸:
然而这段代码不能通过编译。因为 CGPoint 并不是一个常量不能用来定义枚举的值。我们需要为想要支持的自定义类型增加一个扩展让其实现 StringLiteralConvertible 协议。这个协议要求我们实现三个构造方法这三个方法都需要使用一个String类型的参数,并且我们需要将这个字苻串转换成我们需要的类型(此处是CGSize)
现在就可以来实现我们需要的枚举了,不过这里有一个缺点:初始化的值必须写成字符串形式因为這就是我们定义的枚举需要接受的类型(记住,我们实现了 StringLiteralConvertible因此String可以转化成CGSize类型)
终于,我们可以使用 CGPoint 类型的枚举了需要注意的是,当要獲取真实的 CGPoint 的值的时候我们需要访问枚举的是 rawValue 属性。

看linux的代码时看到类似于这样一荇“

1,"\0test-control" 这么一个字符串如果不把开头的0去掉,后续代码处理不是会与问题

总之就是定义字符串时,第一字符为0是有什么特别的用法麼?


参考资料

 

随机推荐