枚举(Enumerations)
枚举在Swift里面得到了很大的拓展, 使其变得更加简单, 易用且强大.
- 枚举语法(Enumeration Syntax)
与ObjC一样, 枚举通过enum来声明, 例如:
enum CompassPoint {
case North
case South
case East
case West
}
// 如果想简单点, 写成一行也是可以的
enum Planet {
case Mercury, Venus, Earth, Mars, Jupiter, Saturn, Uranus, Neptune
}
获得枚举值也是很简单, 而且苹果还特意进行了一些简化:
var directionToHead = CompassPoint.West
directionToHead = .East // 根据类型就已经知道了East就是CompassPoint的East
如果你够好奇的话, 肯定已经猜到了这个简化还是类型推导的功劳, 下面的代码可以验证:
var directionToHead: CompassPoint = .East
- 用Switch语句来匹配枚举值(Matching Enumeration Values with a Switch Statement)
直接看代码应该会更清晰:
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")
}
// 之前说过的, switch必须要涵盖全部范围, 所以除了枚举外, 一般都要有default,
// 因为我们定义的枚举是可以穷举的, 所以全部写上就不会报错了,
// 个人还是建议对枚举没有特殊要求不要加default, 这样编译器会强制我们处理所有的分支,
// 如果以后加了一个枚举值, 而且这个枚举用的比较多, 可以由编译器进行提醒
- 关联值(Associated Values)
简单说来, 就是Swift里面的枚举并不单单是一个值可以, 它还可以关联(我感觉叫存储还容易理解些)一些别的值, 比如WWDC里面的例子:
enum TrainStatus {
case OnTime
case Delay(Int) // 晚点了几分钟
}
var status = TrainStatus.Delay(10) // 晚点了10分钟
那么假如要取这个值怎么办呢? 继续看:
```
switch status {
case .OnTime:
print("The train is ontime")
case .Delay(let minutes):
print("Delayed (minutes) minutes")
}
// 如果有多个参数, 常规写法是.Delay(let minutes, let station, let reason):
// 所以苹果又帮我们简化了, 可以直接 case let .Delay(minutes, station, reason):
4. 原始值(Raw Values)
和别的语言不同, Swift不会对默认对枚举进行一个"赋初值"的动作, 也就是说, CompassPoint.North和0并不是天然绑定的, 除非我们自己手动写上. 例如:
enum CompassPoint: Int { // 注意这里要写类型了的
case North = 1 // 后面的不写就递增, 当然了, 仅限数值类型的(Double, Float也行)
case South // 第一个都不写就是0
case East // 如果是String的话, 默认就是其字面值, 如.South就是"South"
case West // 其它类型都是全部指定原始值
}
如果有了原始值, 那么我们就能通过原始值来获取枚举了, 例如:
let direction = CompassPoint(rawValue: 1) // .North
// 注意:如果你的rawValue超过了枚举范围, 就会返回nil, 所以这个方式初始化出来的枚举值是Optional的, 使用前要拆包
5. 递归枚举(Recursive Enumerations)
听起来貌似很负责的样子, 为此苹果又来了一个关键字叫, indirect case(必须搭配case或者enum使用, 单独一个indirect是不成关键字的). 其实它的本质就是如果我在枚举里面还要使用这个枚举类型怎么办? 回顾一下C语言实现链表的时候, struct Node里面还有struct Node, 所有我们的next要用struct Node *的形式. 同样的, 这里的枚举也会遇到这个问题, 但是Swift已经没有指针了, 自然就要引入新的关键字了.
直接看官网的例子吧:
enum ArithmeticExpression {
case Number(Int)
// 如果去掉indirect就会报错, 提示你加入
indirect case Addition(ArithmeticExpression, ArithmeticExpression)
indirect case Multiplication(ArithmeticExpression, ArithmeticExpression)
}
// 当然, 多个indirect是可以简化的, 直接写在enum前面, 告知一下
indirect enum ArithmeticExpression {
case Number(Int)
case Addition(ArithmeticExpression, ArithmeticExpression)
case Multiplication(ArithmeticExpression, ArithmeticExpression)
}
使用起来的大概是这样的, 感受一下:
func evaluate(expression: ArithmeticExpression) -> Int {
switch expression {
case .Number(let value):
return value
case .Addition(let left, let right):
return evaluate(left) + evaluate(right)
case .Multiplication(let left, let right):
return evaluate(left) * evaluate(right)
}
}
// evaluate (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))
print(evaluate(product))
// prints "18"
这个东西, 一般用的可能不多, 但是要用的时候要是发现编不过还是很郁闷的, 顺便提一句, Swift里面貌似不能用struct来实现链表了, 试过给struct加indirect会报错, 哈哈, 不过可以用class...如果有方法可以用struct实现, 请一定要指点我一下...
6. 枚举可以有运算属性(Computed Properties)和函数
在Swift里面, 属性分为两种, 一种叫存储属性(Store Properties), 其实就是我们一般的属性, 一种叫运算属性, 二者的区别, 我总结起来就是看有没有实体, 如果有实体的话, 就是存储属性, 否则就是运算属性, 举个例子, 很多类,结构体或者枚举的description都是运算属性:
enum CompassPoint: Float {
var description:String {
return "value is (self)" // 运算属性分get和set, 目前这种写法是get, 以后再讲set
}
case North
case South
case East
case West
func desc() {
print("value is (self)")
}
}
var cp: CompassPoint = .South
cp.description // 打印出"value is South"
cp.desc() // 打印出"value is South\n"
想象力丰富的同学肯定想到了, 虽然不能有存储属性, 但是我们可以有关联值, 从某种程度上来代替存储属性来实现一些方案也是可以的.
好了枚举差不多到这里, 具体细节参考[官方文档](https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Enumerations.html#//apple_ref/doc/uid/TP40014097-CH12-ID145)
###类与结构体(Classes and Structures)
这一章只对类和结构体进行一些简单的介绍, 着重会介绍苹果对使用类还是结构体的一些指导意见.
1. 类与结构体对比(Comparing Classes and Structures)
二者都能实现的:
a. 定义存储属性
b. 定义方法
c. 定义下标(subscripts)来访问它们的值
d. 定义初始化方法
e. 可以拓展默认实现
f. 可以实现协议
和大多数语言一样, 下面是只有类能实现的:
a. 继承
b. 类型转换允许你在运行时检查和解释实例类型
c. 析构方法(Deinitializers)允许你在释放类实例持有的一些资源
d. 引用计数
2. 基本语法
和其它语言没有多大区别, 直接看例子吧:
//定义类和结构体:
struct Resolution {
var width = 0
var height = 0
}
class VideoMode {
var resolution = Resolution()
var interlaced = false
var frameRate = 0.0
var name: String?
}
// 获取二者实例:
let someResolution = Resolution()
let someVideoMode = VideoMode()
// 访问属性:
print("The width of someResolution is (someResolution.width)")
print("The width of someVideoMode is (someVideoMode.resolution.width)")
// 设置属性:
someVideoMode.resolution.width = 1280
print("The width of someVideoMode is now (someVideoMode.resolution.width)")
// 结构体的成员初始化函数:
let vga = Resolution(width: 640, height: 480) // 编译器自动合成
3. 结构体和枚举值是值类型:
用例子可以证明之:
// 结构体:
let hd = Resolution(width: 1920, height: 1080)
var cinema = hd
cinema.width = 2048
print("cinema is now (cinema.width) pixels wide")
// prints "cinema is now 2048 pixels wide"
print("hd is still (hd.width) pixels wide")
// prints "hd is still 1920 pixels wide"
// 枚举:
enum CompassPoint {
case North, South, East, West
}
var currentDirection = CompassPoint.West
let rememberedDirection = currentDirection
currentDirection = .East
if rememberedDirection == .West {
print("The remembered direction is still .West")
}
// prints "The remembered direction is still .West"
4. 类是引用类型
这个例子也不需要举了, 肯定是这样的, 主要讲下接下来的2个细节吧:
a. 全等操作符(===), 也有叫恒等的: 引入这个东西还是因为没有指针啊, 所以需要一个操作符来比较2个实例是不是同一个东西, 所以就有了===. 这个和相等操作符的区别是, 相等操作符会比较其内容, 而不是地址.
b. 苹果特意解释了一些关于指针的事情: Swift中指向引用类型的常/变量和C语言的指针很类似, 但是却不是直接指向内存中的地址, 并且我们也不需要写*号来表示我们要创建一个引用类型, 引用类型和Swift里面的其它类型的定义是基本一致的. (感觉说了等于没说, 其实就是告诉我们, 没指针了)
5. 类与结构体的选择(Choosing Between Classes and Structures)
苹果给出了一系列的指导意见, 如果满足下面的一个或者多个条件, 就考虑用结构体吧:
a. 这个结构的主要目的是为了封装一些相对简单的数据
b. 这个结构值被赋值或者被传递的时候, 它被复制是合理的
c. 这个结构内部的存储属性类型也是值类型, 并且它们也期望被复制而不是被引用
d. 这个结构不需要从其它类型中集成属性
下面是一些例子:
a. 几何图形: 可能只会封装width和height, 它们都是Double类型的
b. 表示范围: 可能只会封装start和length, 它们都是Int类型的
c. 3D坐标系: 可能只会封装x,y,z 它们都是Double类型的
6. 字符串,数组和字典的赋值和复制(Assignment and Copy Behavior for Strings, Arrays, and Dictionaries)
如之前所说, 这三种类型都是值类型, 查看一下定义就知道它们是用结构体实现的. 需要注意的是, 虽说这三个值类型在我们的代码中总是会被复制的, 然而, Swift会很智能地只在真正需要复制的时候才会复制. Swift会管理所有的值复制来优化性能, 所以我们不需要不敢赋值, 唯恐影响性能.
对苹果的声明, 我做了一个测试, 可惜并没有得到预想的结果, 不知道是不是测试的姿势不对, 也可能是没开启优化选项? 测试代码和结果如下:
let str = "123"
let str2 = str
print("(str)(str2)")
let str3 = str
let str4 = str
print("(str3)(str4)")
let str5 = str
let str6 = str
print("(str5)_(str6)")
print("======")
上面的代码, 如果是有优化的话, 上面6个String应该只用一份就够了, 但是我得到的结果是这样的:
(lldb) po unsafeAddressOf("123")
▿ 0x00007fdd58519f10
- pointerValue : 140588646244112
(lldb) po unsafeAddressOf(str)
▿ 0x00007fdd5860fba0
- pointerValue : 140588647250848 // a
(lldb) po unsafeAddressOf(str2)
▿ 0x00007fdd5860fba0
- pointerValue : 140588647250848 // a
(lldb) po unsafeAddressOf(str3)
▿ 0x00007fdd5b9669b0
- pointerValue : 140588701084080 // b
(lldb) po unsafeAddressOf(str4)
▿ 0x00007fdd5b9669b0
- pointerValue : 140588701084080 // b
(lldb) po unsafeAddressOf(str5)
▿ 0x00007fdd5b96a310
- pointerValue : 140588701098768
(lldb) po unsafeAddressOf(str6)
▿ 0x00007fdd58401b70
- pointerValue : 140588645096304
从地址可以看出来, 6份按道理同样的字符串, 只有2块区域被复用了, 而且只有一次, 说明内存里面是有4份一样是String(如果不算"123"这个全局变量), 如果有正确的测试姿势, 请回复一下哈. (误!)
// 2016-3-17更正:
上面用unsafeAddressOf打出不同的地址应该是和String实现的方式有关, 实际上真正字符串数据存储的地方在String._core._baseAddress(在Debug模式下, 展开String可以看到), 因此上面这个例子不能说明苹果没有优化, 实际上, 对于值类型, 苹果的原则是"copy on write", 也就是说, 只有在真正修改的时候才会执行copy操作, 可以通过下面的代码进行验证:
let str = "123"
var str2 = str // 这个时候可以看到str2._core._baseAddress与str1的相同, 直到...
str2.appendContentsOf("4") // str2._core._baseAddress被修改了
好了, 结构体和类的基本概念就到这, 明天出差, 之后再更新咯~~~
细节参考[官方文档](https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/ClassesAndStructures.html#//apple_ref/doc/uid/TP40014097-CH13-ID82)