1、基础部分

Swift 包含了 C 和 Objective-C 上所有基础数据类型,Int 表示整型值; DoubleFloat 表示浮点型值; Bool 是布尔型值;String 是文本型数据。 Swift 还提供了三个基本的集合类型,ArraySetDictionary

Swift 还增加了 Objective-C 中没有的高阶数据类型比如元组(Tuple)。元组可以让你创建或者传递一组数据,比如作为函数的返回值时,你可以用一个元组可以返回多个值。

1.1 声明常量和变量

let 来声明常量,用 var 来声明变量

  1. let maximumNumberOfLoginAttempts = 10
  2. var currentLoginAttempt = 0

声明多个常量或者多个变量,用逗号隔开:

  1. var x = 0.0, y = 0.0, z = 0.0

1.2 类型注解

声明常量或者变量的时候可以加上类型注解(type annotation),需要在常量或者变量名后面加上一个冒号和空格,然后加上类型名称,声明中的冒号代表着“是…类型”

  1. var welcomeMessage: String

定义多个同样类型的变量,用逗号分割,并在最后一个变量名之后添加类型注解:

  1. var red, green, blue: Double

1.3 常量和变量的命名

常量和变量名可以包含任何字符,包括 Unicode 字符:

  1. let π = 3.14159
  2. let 你好 = "你好世界"
  3. let 🐶🐮 = "dogcow"

常量与变量名不能包含数学符号,箭头,保留的(或者非法的)Unicode 码位,连线与制表符。

注意: 如果你需要使用与 Swift 保留关键字相同的名称作为常量或者变量名,你可以使用反引号(`)将关键字包围的方式将其作为名字使用。

1.4 注释

  1. // 这是一个注释
  2. /* 这也是一个注释,
  3. 但是是多行的 */
  4. /* 这是第一个多行注释的开头
  5. /* 这是第二个被嵌套的多行注释 */
  6. 这是第一个多行注释的结尾 */

1.5 分号

多条语句在同一行必须加上;

  1. let cat = "🐱"; print(cat)
  2. // 输出“🐱”

1.6 整数

Swift 提供了8、16、32和64位的有符号和无符号整数类型。
不同整数类型的 minmax 属性来获取对应类型的最小值和最大值:

  1. let minValue = UInt8.min // minValue 为 0,是 UInt8 类型
  2. let maxValue = UInt8.max // maxValue 为 255,是 UInt8 类型

1.7 类型安全和类型推断

Swift 是一个类型安全(type safe)的语言,它会在编译你的代码时进行类型检查(type checks),并把不匹配的类型标记为错误。

当你声明常量或者变量并赋初值的时候类型推断非常有用。当你在声明常量或者变量的时候赋给它们一个字面量(literal value 或 literal)即可触发类型推断。

例如,如果你给一个新常量赋值 42 并且没有标明类型,Swift 可以推断出常量类型是 Int ,因为你给它赋的初始值看起来像一个整数:

  1. let meaningOfLife = 42
  2. // meaningOfLife 会被推测为 Int 类型

1.8 数值型字面量

整数字面量可以被写作:

  • 一个十进制数,没有前缀
  • 一个二进制数,前缀是 0b
  • 一个八进制数,前缀是 0o
  • 一个十六进制数,前缀是 0x
    1. let decimalInteger = 17
    2. let binaryInteger = 0b10001 // 二进制的17
    3. let octalInteger = 0o21 // 八进制的17
    4. let hexadecimalInteger = 0x11 // 十六进制的17

数值类字面量可以包括额外的格式来增强可读性。整数和浮点数都可以添加额外的零并且包含下划线,并不会影响字面量

  1. let paddedDouble = 000123.456
  2. let oneMillion = 1_000_000
  3. let justOverOneMillion = 1_000_000.000_000_1

1.9 整数和浮点数转换

  1. let three = 3
  2. let pointOneFourOneFiveNine = 0.14159
  3. let pi = Double(three) + pointOneFourOneFiveNine
  4. // pi 等于 3.14159,所以被推测为 Double 类型

1.10 类型别名

  1. typealias AudioSample = UInt16
  2. var maxAmplitudeFound = AudioSample.min
  3. // maxAmplitudeFound 现在是 0

1.11 布尔值

注意: 如果你在需要使用 Bool 类型的地方使用了非布尔值,Swift 的类型安全机制会报错

  1. let i = 1
  2. if i {
  3. // 这个例子不会通过编译,会报错
  4. }
  5. -------------------------
  6. let i = 1
  7. if i == 1 {
  8. // 这个例子会编译成功
  9. }

1.12 元组

元组(tuples)把多个值组合成一个复合值。元组内的值可以是任意类型,并不要求是相同类型。
下面这个例子中,(404, "Not Found") 是一个描述 HTTP 状态码(HTTP status code)的元组

  1. let http404Error = (404, "Not Found")
  2. // http404Error 的类型是 (Int, String),值是 (404, "Not Found")

(404, "Not Found") 元组把一个 Int 值和一个 String 值组合起来表示 HTTP 状态码的两个部分:一个数字和一个人类可读的描述。这个元组可以被描述为“一个类型为 (Int, String) 的元组”。

你可以将一个元组的内容分解(decompose)成单独的常量和变量,然后你就可以正常使用它们了:

  1. let (statusCode, statusMessage) = http404Error
  2. print("The status code is \(statusCode)")
  3. // 输出“The status code is 404”
  4. print("The status message is \(statusMessage)")
  5. // 输出“The status message is Not Found”

你可以在定义元组的时候给单个元素命名:

  1. let http200Status = (statusCode: 200, description: "OK")

给元组中的元素命名后,你可以通过名字来获取这些元素的值:

  1. print("The status code is \(http200Status.statusCode)")
  2. // 输出“The status code is 200”
  3. print("The status message is \(http200Status.description)")
  4. // 输出“The status message is OK”

作为函数返回值时,元组非常有用。一个用来获取网页的函数可能会返回一个 (Int, String) 元组来描述是否获取成功。和只能返回一个类型的值比较起来,一个包含两个不同类型值的元组可以让函数的返回信息更有用。

1.13 可选类型

使用可选类型(optionals)来处理值可能缺失的情况

  1. var serverResponseCode: Int? = 404
  2. // serverResponseCode 包含一个可选的 Int 值 404

如果你声明一个可选常量或者变量但是没有赋值,它们会自动被设置为 nil

  1. var surveyAnswer: String?
  2. // surveyAnswer 被自动设置为 nil

注意 nil 不能用于非可选的常量和变量。如果你的代码中有常量或者变量需要处理值缺失的情况,请把它们声明成对应的可选类型。Swift 的 nil 和 Objective-C 中的 nil 并不一样。在 Objective-C 中,nil 是一个指向不存在对象的指针。在 Swift 中,nil 不是指针——它是一个确定的值,用来表示值缺失。任何类型的可选状态都可以被设置为 nil ,不只是对象类型。

1.14 if 语句以及强制解析

你可以使用 if 语句和 nil 比较来判断一个可选值是否包含值。你可以使用“相等”(==)或“不等”(!=)来执行比较。

  1. if convertedNumber != nil {
  2. print("convertedNumber contains some integer value.")
  3. }
  4. // 输出“convertedNumber contains some integer value.”

注意
使用 ! 来获取一个不存在的可选值会导致运行时错误。使用 ! 来强制解析值之前,一定要确定可选包含一个非 nil 的值。

1.15 可选绑定

使用可选绑定(optional binding)来判断可选类型是否包含值,如果包含就把值赋给一个临时常量或者变量。可选绑定可以用在 ifwhile 语句中,这条语句不仅可以用来判断可选类型中是否有值,同时可以将可选类型中的值赋给一个常量或者变量。

像下面这样在 if 语句中写一个可选绑定:

  1. if let constantName = someOptional {
  2. statements
  3. }

你可以包含多个可选绑定或多个布尔条件在一个 if 语句中,只要使用逗号分开就行。只要有任意一个可选绑定的值为 nil,或者任意一个布尔条件为 false,则整个 if 条件判断为 false

  1. if let firstNumber = Int("4"), let secondNumber = Int("42"), firstNumber < secondNumber && secondNumber < 100 {
  2. print("\(firstNumber) < \(secondNumber) < 100")
  3. }

1.16 错误处理

你可以使用 错误处理(error handling) 来应对程序执行中可能会遇到的错误条件。

  1. func canThrowAnError() throws {
  2. // 这个函数有可能抛出错误
  3. }
  4. do {
  5. try canThrowAnError()
  6. // 没有错误消息抛出
  7. } catch {
  8. // 有一个错误消息抛出
  9. }

1.17 断言和先决条件

断言和先决条件是在运行时所做的检查。你可以用他们来检查在执行后续代码之前是否一个必要的条件已经被满足了。如果断言或者先决条件中的布尔条件评估的结果为 true(真),则代码像往常一样继续执行。如果布尔条件评估结果为 false(假),程序的当前状态是无效的,则代码执行结束,应用程序中止。

你可以调用 Swift 标准库的 assert(_:_:file:line:) 函数来写一个断言。向这个函数传入一个结果为 true或者 false 的表达式以及一条信息,当表达式的结果为 false 的时候这条信息会被显示:

  1. let age = -3
  2. assert(age >= 0, "A person's age cannot be less than zero")
  3. // 因为 age < 0,所以断言会触发


2、基础运算符

Swift 支持大部分标准 C 语言的运算符,且为了减少常见编码错误做了部分改进。如:赋值符(=)不再有返回值,这样就消除了手误将判等运算符(==)写成赋值符导致代码错误的缺陷。

Swift 还提供了 C 语言没有的区间运算符,例如 a..<ba...b,这方便我们表达一个区间内的数值。

2.1 赋值运算符

如果赋值的右边是一个多元组,它的元素可以马上被分解成多个常量或变量:

  1. let (x, y) = (1, 2)
  2. // 现在 x 等于 1,y 等于 2

与 C 语言和 Objective-C 不同,Swift 的赋值操作并不返回任何值。所以下面语句是无效的:

  1. if x = y {
  2. // 此句错误,因为 x = y 并不返回任何值
  3. }

2.2 一元负号运算符

  1. let three = 3
  2. let minusThree = -three // minusThree 等于 -3
  3. let plusThree = -minusThree // plusThree 等于 3, 或 "负负3"

一元负号符(-)写在操作数之前,中间没有空格。

2.3 比较运算符(Comparison Operators)

  1. (1, "zebra") < (2, "apple") // true,因为 1 小于 2
  2. (3, "apple") < (3, "bird") // true,因为 3 等于 3,但是 apple 小于 bird
  3. (4, "dog") == (4, "dog") // true,因为 4 等于 4,dog 等于 dog

注意 Swift 标准库只能比较七个以内元素的元组比较函数。如果你的元组元素超过七个时,你需要自己实现比较运算符。

2.4 空合运算符(Nil Coalescing Operator)

空合运算符a ?? b)将对可选类型 a 进行空判断,如果 a 包含一个值就进行解包,否则就返回一个默认值 b。表达式 a 必须是 Optional 类型。默认值 b 的类型必须要和 a 存储值的类型保持一致。

空合运算符是对以下代码的简短表达方法:

  1. a != nil ? a! : b

注意 如果 a 为非空值(non-nil),那么值 b 将不会被计算。这也就是所谓的短路求值

2.5 区间运算符(Range Operators)

闭区间运算符a...b)定义一个包含从 ab(包括 ab)的所有值的区间。a 的值不能超过 b

  1. for index in 1...5 {
  2. print("\(index) * 5 = \(index * 5)")
  3. }

半开区间运算符a..<b)定义一个从 ab 但不包括 b 的区间。 之所以称为半开区间,是因为该区间包含第一个值而不包括最后的值。

  1. let names = ["Anna", "Alex", "Brian", "Jack"]
  2. let count = names.count
  3. for i in 0..<count {
  4. print("第 \(i + 1) 个人叫 \(names[i])")
  5. }
  6. // 第 1 个人叫 Anna
  7. // 第 2 个人叫 Alex
  8. // 第 3 个人叫 Brian
  9. // 第 4 个人叫 Jack

2.6 单侧区间

闭区间操作符有另一个表达形式,可以表达往一侧无限延伸的区间 —— 例如,一个包含了数组从索引 2 到结尾的所有值的区间。在这些情况下,你可以省略掉区间操作符一侧的值。这种区间叫做单侧区间,因为操作符只有一侧有值。例如:

  1. for name in names[2...] {
  2. print(name)
  3. }
  4. // Brian
  5. // Jack
  6. for name in names[...2] {
  7. print(name)
  8. }
  9. // Anna
  10. // Alex
  11. // Brian

3、字符串和字符

3.1 多行字符串字面量

如果你需要一个字符串是跨越多行的,那就使用多行字符串字面量 — 由一对三个双引号包裹着的具有固定顺序的文本字符集

  1. let quotation = """
  2. The White Rabbit put on his spectacles. "Where shall I begin,
  3. please your Majesty?" he asked.
  4. "Begin at the beginning," the King said gravely, "and go on
  5. till you come to the end; then stop."
  6. """

3.2 字符串字面量的特殊字符

字符串字面量可以包含以下特殊字符:

  • 转义字符 \0(空字符)、\\(反斜线)、\t(水平制表符)、\n(换行符)、\r(回车符)、\"(双引号)、\'(单引号)。
  • Unicode 标量,写成 \u{n}(u 为小写),其中 n 为任意一到八位十六进制数且可用的 Unicode 位码。
    1. let wiseWords = "\"Imagination is more important than knowledge\" - Einstein"
    2. // "Imageination is more important than knowledge" - Enistein
    3. let dollarSign = "\u{24}" // $,Unicode 标量 U+0024
    4. let blackHeart = "\u{2665}" // ♥,Unicode 标量 U+2665
    5. let sparklingHeart = "\u{1F496}" // 💖,Unicode 标量 U+1F496

3.3 初始化空字符串

要创建一个空字符串作为初始值,可以将空的字符串字面量赋值给变量,也可以初始化一个新的 String 实例:

  1. var emptyString = "" // 空字符串字面量
  2. var anotherEmptyString = String() // 初始化方法
  3. // 两个字符串均为空并等价。
  4. if emptyString.isEmpty {
  5. print("Nothing to see here")
  6. }
  7. // 打印输出:“Nothing to see here”

3.4 字符串是值类型

在 Swift 中 String 类型是值类型。如果你创建了一个新的字符串,那么当其进行常量、变量赋值操作,或在函数/方法中传递时,会进行值拷贝。在前述任一情况下,都会对已有字符串值创建新副本,并对该新副本而非原始字符串进行传递或赋值操作。

3.5 使用字符

字符串可以通过传递一个值类型为 Character 的数组作为自变量来初始化:

  1. let catCharacters: [Character] = ["C", "a", "t", "!", "🐱"]
  2. let catString = String(catCharacters)
  3. print(catString)
  4. // 打印输出:“Cat!🐱”

你可以用 append() 方法将一个字符附加到一个字符串变量的尾部:

  1. let exclamationMark: Character = "!"
  2. welcome.append(exclamationMark)
  3. // welcome 现在等于 "hello there!"

注意 你不能将一个字符串或者字符添加到一个已经存在的字符变量上,因为字符变量只能包含一个字符。

3.6 计算字符数量

注意在 Swift 中,使用可拓展的字符群集作为 Character 值来连接或改变字符串时,并不一定会更改字符串的字符数量。

  1. let unusualMenagerie = "Koala 🐨, Snail 🐌, Penguin 🐧, Dromedary 🐪"
  2. print("unusualMenagerie has \(unusualMenagerie.count) characters")
  3. // 打印输出“unusualMenagerie has 40 characters”

注意在 Swift 中,使用可拓展的字符群集作为 Character 值来连接或改变字符串时,并不一定会更改字符串的字符数量。

  1. var word = "cafe"
  2. print("the number of characters in \(word) is \(word.count)")
  3. // 打印输出“the number of characters in cafe is 4”
  4. word += "\u{301}" // 拼接一个重音,U+0301
  5. print("the number of characters in \(word) is \(word.count)")
  6. // 打印输出“the number of characters in café is 4”

3.7 字符索引

使用 startIndex 属性可以获取一个 String 的第一个 Character 的索引。使用 endIndex 属性可以获取最后一个 Character 的后一个位置的索引。因此,endIndex 属性不能作为一个字符串的有效下标

通过调用 Stringindex(before:)index(after:) 方法,可以立即得到前面或后面的一个索引。你还可以通过调用 index(_:offsetBy:) 方法来获取对应偏移量的索引,这种方式可以避免多次调用 index(before:)index(after:) 方法。

  1. let greeting = "Guten Tag!"
  2. greeting[greeting.startIndex]
  3. // G
  4. greeting[greeting.index(before: greeting.endIndex)]
  5. // !
  6. greeting[greeting.index(after: greeting.startIndex)]
  7. // u
  8. let index = greeting.index(greeting.startIndex, offsetBy: 7)
  9. greeting[index]
  10. // a

3.8 子字符串

当你从字符串中获取一个子字符串 —— 例如,使用下标或者 prefix(_:) 之类的方法 —— 就可以得到一个 SubString 的实例,而非另外一个 String

  1. let greeting = "Hello, world!"
  2. let index = greeting.firstIndex(of: ",") ?? greeting.endIndex
  3. let beginning = greeting[..<index]
  4. // beginning 的值为 "Hello"
  5. // 把结果转化为 String 以便长期存储。
  6. let newString = String(beginning)

3.9 前缀/后缀相等

通过调用字符串的 hasPrefix(_:)/hasSuffix(_:) 方法来检查字符串是否拥有特定前缀/后缀,两个方法均接收一个 String 类型的参数,并返回一个布尔值。

  1. let romeoAndJuliet = [
  2. "Act 1 Scene 1: Verona, A public place",
  3. "Act 1 Scene 2: Capulet's mansion",
  4. "Act 1 Scene 3: A room in Capulet's mansion",
  5. "Act 1 Scene 4: A street outside Capulet's mansion",
  6. "Act 1 Scene 5: The Great Hall in Capulet's mansion",
  7. "Act 2 Scene 1: Outside Capulet's mansion",
  8. "Act 2 Scene 2: Capulet's orchard",
  9. "Act 2 Scene 3: Outside Friar Lawrence's cell",
  10. "Act 2 Scene 4: A street in Verona",
  11. "Act 2 Scene 5: Capulet's mansion",
  12. "Act 2 Scene 6: Friar Lawrence's cell"
  13. ]
  1. var act1SceneCount = 0
  2. for scene in romeoAndJuliet {
  3. if scene.hasPrefix("Act 1 ") {
  4. act1SceneCount += 1
  5. }
  6. }
  7. print("There are \(act1SceneCount) scenes in Act 1")
  8. // 打印输出“There are 5 scenes in Act 1”