Swift 编程语言 3.0 (目录参考Xcode帮助文档中的目录, 文档翻译的是EPUB格式的那个3.0文档,取自Swift官网)
Language Guide
[TOC]
1 语言基础(The Basics)
Swift 中提供了许多对应于之前C和OC中的数据类型, 如整形 Int , 浮点型 Double 和 Float , 布尔类型Bool, 文本类型String. Swift中还提供了三个重要的结构类型, 数组 Array , 集合 Set , 字典 Dictionary .
和C一样, Swift 也通过变量来存储和使用数据. 另外 Swift 中对常量作了扩展, 使得 Swift 中的常量比C中的要强大许多. 通过常量的使用, 可以让代码更加安全和清晰.
相比OC, 在 Swift 中还新增了许多新的数据类型, 比如元组 Tuple , 使用元组可以创建或传递成组的数据, 另外还可以通过函数返回元组.
Swift中还引入了 optional 的概念, 用于有值或无值的表示. Optional 可以表示两种意思, 要么是”这里有值, 而且值是x”, 要么就是”这里没有任何值”. Optional 的使用类似于使用nil, 只是Optional可以用在任何类型上, 而不仅仅是类.
Swift 是一种类型安全语言(type-safe), 即语言自身机制就可以帮助用户正确使用类型和数据. 比如某变量需要赋值一个 String, 类型安全就可以绝对避免用户错误赋值 Int.
1.1 常量和变量
常量和变量都具有名字, 并且常量在定义之后值就不能再进行修改.
1.1.1 常量和变量的定义
使用 let 关键字定义常量, 使用 var 关键字定义变量.
let max = 10
var temp = 0
也可以在同一行定义多个常量或变量:
var x = 0.0, y = 0.0, z = 0.0
NOTE
若某个量中存放的数据是不变的, 那就将它定义为常量.
1.1.2 类型声明
在定义常量或变量时, 可以显式声明其类型:
var welcomeMessage: String
可以在同一行显式声明多个相同数据类型的变量:
var red, green, blue: Double
NOTE
实际编程中很少使用类型声明, 因为在提供了初值之后, 该常量或变量的类型就已经确定下来(类型推导).
1.1.3 常量和变量的命名
和C类似, 只是多了Unicode. 虽然Swift中可以用(`)内包含关键字的办法使用关键字名称, 但应尽量避免.
1.1.4 打印常量和变量
使用 print(_:separator:terminator:)
函数可以打印常量或变量:
print(welcomeMessage)
print 函数在Swift中是一个全局函数, 负责将内容输出到合适的 output 上. 比如使用 Xcode 时, print 函数会将内容输出到 Xcode 的 console 中. separator 和 terminator 参数都有默认值, 一般使用时可以忽略, 也可以指定参数:
print(someValue, terminator: "")
默认的 terminator 参数值是换行, 上面的代码指定其为空.
Swift 中使用”字符串内插”来处理字符串内的变量或常量:
let someValue = 1.0
print("value is \(someValue)") //Prints "value is 1.0"
1.2 注释
基本和C中的类似, 只是多行注释在 Swift 中是可以进行嵌套的.
1.3 分号
Swift 中不强制要求表达式末尾加分号, 但如果想在单行写多条表达式, 则必须以分号分隔表达式.
let someValue = "abc";print(someValue)
//Prints "abc"
1.4 整型
整数类型和数学上的整数定义类似, 42 或 -23 都是整数类型的值.
Swift中提供了有符号或无符号的整数类型, 并且有 8, 16, 32, 64bit 四种形式的整数类型. 并且命名和C中类似, 比如无符号的8位整型为 UInt8, 32位有符号整型为 Int32.
Swift中所有的数据类型都遵循大写化的命名规则.
1.4.1 整型的范围
可以使用整型的 min
或 max
属性查看整型的范围:
let minValue = UInt8.min //minValue is equal to 0, and is of type UInt8.
let maxValue = UInt8.max //maxValue is equal to 255, and is of type UInt8.
1.4.2 Int类型
大多数情况下, 用户都无需考虑使用哪种特殊种类的整型, Swift中提供了额外的整数类型 Int , 并且它的大小和平台相关:
- 当在32位平台使用时, Int 的大小为32位, 即和 Int32 相同.
- 当在64位平台使用时, Int 的大小为64位, 即和 Int64 相同.
若没有特殊需要, 一般在代码中都使用 Int 类型来存放整数.
1.4.3 UInt类型
和 Int 类似, Swift 中提供了一种额外的无符号整形 UInt, 大小同样和平台相关:
- 当在32位平台使用时, UInt 的大小为32位, 即和 UInt32 相同.
- 当在64位平台使用时, UInt 的大小为64位, 即和 UInt64 相同.
NOTE
无特殊需要时, 都使用 Int 存放整型数据, 这样可以保证最少的类型转换.
1.5 浮点类型
诸如 3.1415, -273.15 这样的数都使用浮点类型存放.
相比整型, 浮点类型可以表示的范围更广, 在 Swift 中提供了两种浮点类型:
- Double 类型: 64位浮点数
- Float 类型: 32位浮点数
NOTE
Double 的数字精度为15位, Float 为6位. 根据工作需要及数据范围选择合适的浮点类型, 当两种类型都合适时, 尽量选择 Double.
1.6 类型安全和类型推导
Swift 是一种类型安全语言, 类型安全语言要求你在使用变量时必须明确其类型. 并且由于Swift 是类型安全的, 故在编译时, 编译器会对代码进行类型检查.
Swift 的另外一个特色是类型推导, 即不需要每处都显式指定类型.
1.7 数值常量
整型常量可以写成:
- 无前缀的十进制数
- 前缀为
0b
的二进制数 - 前缀为
0o
的八进制数 - 前缀为
0x
的十六进制数
如整数17 上述四种表示分别为:
- 17
- 0b10001
- 0o21
- 0x11
浮点型常量可以写成无前缀十进制数, 或者十六进制的(前缀0x). 但二者小数点左右都必须有数字. 十进制表示的浮点数也可以以指数形式表示, 以e来指定(10的多少次方). 十六进制浮点数必须使用p来指定指数(2的多少次方):
- 1.25e2 表示 125.0
- 1.25e-2 表示 0.0125
- 0xFp2 对应十进制的 15 x 2的2次方, 或60.0
- 0xFp-2 对应十进制的 15 x 2的-2次方, 或3.75
数值常量可以带格式化字符以帮助阅读, 整数或浮点数都可以使用多个0或下划线作为格式化字符, 并且不会对数值产生影响:
- 123.456 可以格式化表示为 000123.456
- 1_000
- 1_000.000
1.8 数值类型转换
尽量使用Int类型的常量或变量来存放整型数值, 即便知道这些数值是非负的, 这样可以保证最低限度的数值类型转换.
只有当任务的确有特殊需要时, 再考虑使用其他类型.
1.8.1 整型间的类型转换
由于有8, 16, 32, 64位整型, 并且存在有符号或无符号整型, 故整型之间也存在许多的类型转换.
Int8 表示范围为 -128 到 127, 而 UInt8 表示范围为 0 到 255, 当一个数值不能满足类似范围要求而存放到该类型中, 则在编译时会报错:
let cannotBeNegative: UInt8 = -1 //这样的就会报错
let tooBig: Int8 = Int8.max + 1 //超过范围也报错
上面可以看到, 不同整数类型都有不同的存放范围, 所以当有特殊要求使用特殊类型时, 必须针对不同场景来判断, 选择合适类型. 而编译器的这种超限报错, 可以最大限度减少程序运行中出现错误的机会.
由于不同类型无法直接相加, 故下面的例子中将用到类型转换:
let twoThousand: UInt16 = 2_000
let one: UInt8 = 1
let twoThousandAndOne = twoThousand + UInt16(one) //转换后相加
注意转换后的范围只有是包含之前的范围, 才可以保证转换成功.
Swift 中默认的类型转换方式为: someType(someValue)
. 这样的转换实际是因为UInt16有一个初始化方法, 它接受一个UInt8 类型的数值, 产生 UInt16 类型的对象, 从而实现UInt8 到 UInt16的转换. 故并非支持所有的类型, 如果遇到不支持的类型想要进行这样的类型转换, 可以使用 Extension 来扩展该类型的初始化方法以接受更多类型即可.
1.8.2 整型和浮点型之间的转换
整型和浮点型间的类型转换必须显式地进行:
let three = 3
let pointOneFourOneFiveNine = 0.14159
let pi = Double(three) + pointOneFourOneFiveNine //显式转换后相加
上面代码中使用 three 来生成了一个 Double.
而浮点型到整型也是类似:
let pi = 3.14159
let integerPi = Int(pi) //使用pi转换为Int
转换时都是将小数部分截断, 无论是正数还是负数:
- 4.75 转换后为 4
- -3.73 转换后为 -3
NOTE
变量间类型转换的规则与常量间的类型转换不同.
由于常量在编译之前是没有显式类型的, 故只有当编译时对常量进行类型推导并进行计算, 故整型常量与浮点常量可以直接进行运算:
let pi = 3 + 0.14159
1.9 类型别名
(Type aliases )
类型别名即当前类型的替代名称. 使用 typealias
关键字来声明类型别名.
当想让类型在上下文中更具有自身意义时, 可以使用类型别名, 例如当某类型存放的是特殊类型的数值时:
typealias AudioSample = UInt16
var maxAmplitudeFound = AudioSample.min //更具可读性且在上下文中意义更好识别
声明了类型别名后, 即可在任何位置使用, 当调用 AudioSample.min
时, 实际是调用 UInt16.min
.
1.10 布尔类型
Swift 中拥有基本布尔类型 Bool
, 即逻辑值. Swift中提供两种逻辑常量值, 即: true
和 false
.
Bool 类型在条件语句中尤为有用:
if turnipsAreDelicious {
print("MM, tasty turnips!")
}else {
print("...")
}
Swift 中的类型安全机制可以保证其他类型不会被误用为 Bool:
let i = 1
if i { //这里会报错, 因为将整型误用为布尔类型
...
}
上述代码需要修改为如下的形式:
let i = 1
if i == 1 { //这样才不会报错
...
}
上面代码中 i == 1
的结果就是 Bool 类型的.
这样的做法可以避免类型误用, 并且使得代码更加清晰.
1.11 元组
元组(tuple)即一组值的集合. 元组中的值类型是任意的, 且不必全部都是相同类型. 比如 (404, "not found")
即为一个元组. 而这个元组的类型可以描述为 (Int, String)
.
元组中可以包含任意多个元素, 并且其中元素类型任意.
可以将元组在一条语句中将所有元素赋值给变量:
let http404Error = (404, "not found")
let (statusCode, statusMessage) = http404Error
//此时 statusCode为404, 类型为Int, 而statusMessage为"not found", 类型为String.
如果只需要元组中单个元素的值, 则可以像如下这样:
let (statusCode, _) = http404Error //statusCode的值为404.
也可以使用下标访问(下标从0开始):
let statusCode = http404Error.0 //statusCode的值为404.
可以为元组中的元素添加名称, 以后则可以通过名称访问:
let http404Error = (statusCode: 404, description: "not found")
//访问时可以直接使用名称访问:
print(http404Error.statusCode) //输出404.
元组的一个最大用处是作为函数的返回值. 比如某个获取网页内容的函数可能返回值为 (Int, String)
类型的元组, 相比于单个返回元素, 这样的返回值更加地方便实用.
NOTE
元组的主要用途是用于暂存一组相关的元素值, 并不适合去存放复杂的数据结构. 假如你想长期存放(超过暂存的范畴)某个数据结构, 则需要将这个数据结构以 class 或 structure 进行建模.
1.12 可选类型
(Optionals) 重点的概念内容.
一个可选类型表示的只有两种可能性: 有值, 并且可以对可选量进行解包访问; 或者是无值.
NOTE
可选类型在C或OC中都不存在, 它是Swift中新增的概念. 与它比较接近的就是返回nil或对象的函数, 假设某操作失败, 该函数返回nil, 否则返回一个对象. 但OC中的这种机制只适合于对象, 而不适合结构体, 以及C中的原子类型或是枚举类型. 对于这些类型, OC的解决方案是返回一个特殊的值来表示”没有”, 比如
NSNotFound
来表明无值.而Swift中使用Optional 来让所有类型都得到统一的处理, 无值都是nil, 而不管是什么类型的变量被封装到可选类型中.
例如, Swift 中 Int 类型中有一个方法, 可以将某字符串转换为数值(只要字符串是可转换的话), 比如 String 类型的 “123.456”, 可以转换为 Int 类型的 123.456. 但如果是 “hello world” 参与转换呢?
Swift 中这样的情况都是返回的可选类型封包的变量. 即转换函数返回的是 Int?
类型的, 问号表示这个类型为可选类型, 而 Int 一看便知可选类型中封包的是 Int 型变量, Int 和问号合在一起就表示: 这个类型的变量要么有值, 并且可以解包, 要么就是nil.
1.12.1 nil
如果想将可选类型状态变为”无值”, 则可以将其赋值为nil.
var serverResponseCode: Int? = 404 //当前变量类型为可选Int类型, 值为404
serverResponseCode = nil //赋值nil后, 变量便无值了.
NOTE
Swift 中 nil 只能用在可选类型的赋值上, 这样也就保证了语言内的所有类型的统一处理. 如果某个值在特定情况下会出现无值的情况, 则必须将它声明为可选类型才能够得到恰当处理.
如果在定义可选类型的时候未赋初值, Swift 会自动将其置为 nil.
NOTE
Swift 中 nil 和 OC 中的 nil 意义并不相同. OC 中的 nil 实际是一个指针, 指向的是一个并不存在的对象. 而在 Swift 中, nil 并非一个指针, 它是某种类型的值, 表示的是该类型”无值”的特殊情况.
Swift 中任意类型的可选值都可以被赋值为 nil, 而不仅仅是对象类型.
1.12.2 If 语句和强制解包
可以使用 If 语句来判断可选量是否有值:
if convertNumber != nil {
...
}
当确定可选量中有值后, 就可以对其进行访问, 访问需要先进行解包, 解包操作符是 !
, 感叹号的意思是: 已经知道里面有值, 需要解包访问, 即对可选量进行强制解包:
if convertedNumber != nil {
print("number is \(convertedNumber!)") //强制解包后打印
}
需要注意的是: 只有可选量不是nil的情况下才能使用强制解包, 否则会出现运行时错误.
1.12.3 可选绑定
可选绑定的意思是: 先看看可选量是否是 nil, 如果不是 nil , 则进行解包并将内部值赋值给某变量.
实际上就是将判断可选量是否为 nil 以及解包访问这两步操作合为一条语句.
使用可选绑定的方式有两种, 在 if 语句中使用, 在 while 语句中使用, 下面演示 if 语句中的用法:
if let constantName = someOptional {
//若 someOptional 不为 nil, 则进入语句块中, 此时 constantName 为该可选量解包之后的值
}
//当然也可以用变量来进行可选绑定:
if var constantName = someOptional {
//同样的效果, 用法不同而已.
}
可选绑定集判断与解包于一体, 故不必再在其中使用强制解包操作 !
.
并且可选绑定可以同时对多个可选量进行:
if let first = Int("abc"), second = Int("404") where first < second {
//当任意一个可选绑定不成功, 或where语句为false, 则条件都被判断为不成功
}
NOTE
在可选绑定中定义的常量或变量, 其生命期都只在该可选绑定的语句块范围内.
但使用
guard
语句定义的常量或变量, 生命期是在guard语句行之后. (详见Early Exit)
1.12.4 隐式已解包可选类型
在某些情况下, 有的可选量在初次设定之后就永远都拥有值(设定前为 nil), 则如果在之后的使用过程中还一直对它进行判断有无, 显然是一种浪费资源的行为.
Swift 中对于这种情况的处理方法是: 将这种情况的常量或变量声明为隐式已解包可选类型(Implicitly Unwrapped Optional, 或 IU 可选类型)
在声明 IU可选类型时, 使用 !
替代?
进行声明.
IU 可选类型的主要用途就是在类的初始化过程中. IU 可选量和普通的可选量本质上都是可选类型, 只是在使用时, IU可选量省去解包的一环, 可以像普通常量或变量那样直接使用.
NOTE
假如一个 IU可选类型为nil, 而外界尝试解包访问它, 仍然会造成运行时错误. 因为IU可选量和普通可选量本质都是一个样的.
使用时也可以将 IU可选量像普通的可选量那样去使用.
if assumeNotNilOptional != nil {
//...
}
IU可选量也可以参与可选绑定:
if let someConstant = assumeNotNilOptional {
//...
}
NOTE
只有在确定的情况下才可以使用IU可选类型, 假设该可选量有可能出现nil, 则不要将其声明为IU可选类型.
1.13 错误处理
(Error Handling)
使用 Swift 中的错误处理手段来处理程序在运行时可能遭遇的运行时错误.
Swift 中函数可以使用可选类型返回值来表示成功与失败, 而错误处理允许处理其他情况引起的失败情况, 并且如果需要, 可以将失败情况交由程序的另外部分处理.
当函数遭遇错误时, 会抛出(throw)一个错误(error). 而函数的调用者可以捕捉(catch)这个错误:
func canThrowAnError() throws {
//这个函数可能会抛出错误
}
函数在定义时使用 throws
关键字来表示其可能会抛出错误, 而调用者看到这个函数可能会抛出错误, 自然就要使用 try
来调用这个函数了, 并且错误处理是交由 catch
语句块负责:
do {
try canThrowAnError()
//如果没有错误, 则向下, 否则跳入catch语句块中.
} catch {
//...错误处理.
}
上面的 do catch
语句是 Swift 中特有的, 它制造了一个小的作用域, 使得错误得以在内部进行处理.
下面的代码演示一个典型的多类型错误处理过程:
func makeASandwich() throws {
//...
}
...
do {
try makeASandwich() //尝试制作三明治
eatASandwich() //没有错误, 可以吃三明治
} catch SandwichError.outOfCleanDishes { //无盘子错误
washDishes()
} catch SandwichError.missingIngredients(let ingredients) { //无原料错误
byGroceries(ingredients)
}
1.14 断言
(Assertion)
有时当某些特定条件无法满足时, 程序便无法再继续运行下去. 这样的情况, 可以在代码中使用断言(assertion)来中断代码运行并提供调试那些缺少的或无效的值的机会.
1.14.1 利用断言调试
断言是一种运行时检查手段, 即指定一个条件状态一直为真, 若不满足条件, 则中断运行之后的代码. 使用断言可以检查许多必须的条件程序运行所需的条件状态.
当在 Debug 模式下触发断言, 比如在 Xcode 中构建和运行程序, 则可以明确地知道是在哪个位置发生的无效状态, 并且可以知道当触发断言时程序的所有状态. 并且断言还提供了打印相应信息的功能.
写断言使用到的是 Swift 标准库中提供的全局函数 assert(_:_:file:line:)
. 向这个函数传入假定为 true 或 false 的条件, 以及触发断言时的打印信息:
let age = -3
assert(age >= 0, "A person's age cannot be less than zero.")
代码中当断言触发时, 打印一句话. 只有当 age >= 0 时, 程序才会一直执行, 否则会触发断言并终止程序运行.
assert 函数中的log参数可以不写:
assert(age >= 0)
NOTE
当以优化模式编译时, 断言就会自动关闭, 例如在
Release
模式编译的情况下.
1.14.2 断言使用时机
若某条件当代码运行时必须一直为 true, 而实际又可能会出现 false 的情况时, 就可以使用断言.
下面是一些断言使用的典型情况:
- 当某些时候可能下标超限时
- 函数需要某个参数, 但参数可能无效时
- 需要某可选类型不能为空时
NOTE
断言只是作为调试工具, 而非在程序设计时使用. 当可能出现无效条件时, 断言可以保证找到该处并在开发时进行相应调整, 而不是等到程序发布后才发现可能出现无效条件.