学习Swift语言(二)基本语法

398 阅读15分钟

一、运算符

  • 一元运算符(Unary operators):前缀(prefix)!a,后缀(postfix)a!
  • 二元运算符(Binary operators):a + b
  • 三元运算符(Ternary operators):a ? b : c

1.1 赋值运算符

// 1. 简单赋值
let b = 10
var a = 5

// 2. 按元组赋值
let (x, y) = (1, 2)

// 3. “不支持” if 判断赋值语句
if x = y { // 不能这么用,这里铁定抛编译错误

}

1.2 算术运算符

注意以下三个要点:

  • Swift 默认不允许算术运算的值溢出,这有别于 C 和 Objective-C,若要允许溢出则需要使用溢出运算符(overflow operator),例如:a &+ b
  • Swift 支持运算符实现字符串拼接,例如:"hello, " + "world"
  • 求余运算符-9 % 4的结果是-1,因为:-9 = (4 x -2) + -1

1.3 Compound Assignment Operators

其实就是+=-=*=/=,和 C 语言基本一样,Swift 中应该没有默认的++--运算符。

1.4 比较运算符

和 C 语言一样:==!=><>=<=。Swift 还提供了===!==用于判断两个引用是否指向相同的对象。

// 1. Swift 支持按元组比较,Swift 默认的元组比较元素个数最大限制是7
(1, "zebra") < (2, "apple")   // true because 1 is less than 2; "zebra" and "apple" are not compared
(3, "apple") < (3, "bird")    // true because 3 is equal to 3, and "apple" is less than "bird"
(4, "dog") == (4, "dog")      // true because 4 is equal to 4, and "dog" is equal to "dog"

1.5 三元运算符

官方文档提到三元运算符可以简化代码,但是过度使用会降低代码的可读性。

// 1. Swift 的三元运算符语法
let contentHeight = 40
let hasHeader = true
let rowHeight = contentHeight + (hasHeader ? 50 : 20)

1.6 Nil-Coalescing Operator

Swift 专门为可空类型设计:a ?? b,等价于a != nil ? a! : b,用于给可控类型a设置默认值b

1.7 范围运算符

范围运算符用于生成范围区间Range,包括:

  • ...:闭区间范围运算符,包括头尾;
  • ..<:半开区间范围运算符,包括头不包括尾;
// 1. 闭区间范围运算符,从 1 打到 5
for index in 1...5 {
    print("\(index) times 5 is \(index * 5)")
}

// 2. 半开区间范围运算符,下面用半开区间,因为到 count 索引会溢出
let names = ["Anna", "Alex", "Brian", "Jack"]
let count = names.count
for i in 0..<count {
    print("Person \(i + 1) is called \(names[i])")
}

// 3. 单边1,从目标索引到末尾。打印结果:Brian Jack,也就是索引 2,3
for name in names[2...] {
    print(name)
}

// 4. 单边2,从开头到目标索引。打印结果:Anna Alex Brian,也就是索引 0,1,2
for name in names[...2] {
    print(name)
}

// 5. Swift 利用范围运算符取数组区间,牛逼
for name in names[..<2] {
    print(name)
}

// 6. Swift 范围运算符返回结果就是个 Range
let range = ...5
range.contains(7)   // false
range.contains(4)   // true
range.contains(-1)  // true

1.8 逻辑运算符

和 C 语言一样包括&&||!。下面一段话摘自官方文档,但是实际调试发现let res = (true || false && false)得到的restrue,也就是说&&优先级还是会高于||的。不知道是我理解错了,还是确实是错漏,看来还是尽量用()分隔吧。

Note: The Swift logical operators && and || are left-associative, meaning that compound expressions with multiple logical operators evaluate the leftmost subexpression first.

二、字符和字符串

Swift 的字符串操作语法比 Objective-C 简洁得多,支持+运算符拼接字符串,支持\()形式的字符串插值(interpolation)。Swift 的字符串由一系列编码无关的 Unicode 字符组成,而且支持以各种 Unicode 形式访问其中的字符。

注意:Swift 的String和 Foundation 框架中的NSString是 bridged 的。Foundation 也对NSString做了String的接口扩展。也就是说,只要导入 Foundation,String就可以调用NSString定义的所有接口。

2.1 字符串字面量

Swift 的单行字符串并没什么特殊之处。

let someString = "Some string literal value"

但是多行字符就相当牛逼了。以下代码表示多行字符串,包括中间的空白行

let quotation = """
The White Rabbit put on his spectacles.  "Where shall I begin,
please your Majesty?" he asked.

"Begin at the beginning," the King said gravely, "and go on
till you come to the end; then stop."
"""

注意多行字符的有效区间是:"""起始符的下一行以及"""终结符的上一行之间。因此下面两个字符串头尾都不包含转行符,两者实际上是相等的。

let singleLineString = "These are the same."
let multilineString = """
These are the same.
"""

有时想让代码可读性更好想要在代码中转行,但是字符串内容不转行,此时使用\实现

let softWrappedQuotation = """
The White Rabbit put on his spectacles.  "Where shall I begin, \
please your Majesty?" he asked.

"Begin at the beginning," the King said gravely, "and go on \
till you come to the end; then stop."
"""

Swift 声明多行字符串时,可以带缩进效果。Swift 的字符串内容会忽略"""起始符下一行起始的所有空格,作为多行字符串的整体缩进量。如下图所示,"""起始符下一行起始有 4 个空格,则该字符串会忽略后续所有行的前 4 个空格。

字面量中可以包含特殊字符,例如:

  • 空字符\0、反斜杠\\、制表符\t、转行符\n、回车\r、双引号\"、单引号\'
  • 任意 Unicode 编码字符,用\u{n}表示,其中n为 Unicode 编码值,为 1-8 位十六进制数;

Swift 多行字符串中单个双引号是不需要转义的,甚至两个连续的双引号也无需转义,仅当三个连续双引号时需要转义,方式有两种:1、只转义第一个双引号;2、三个双引号都转义。例程如下:

let threeDoubleQuotationMarks = """
Escaping the first quotation mark \"""
Escaping all three quotation marks \"\"\"
"""

有时想忽略字符串字面量中的转义字符,可以使用#符号实现。例如打印#"Line 1\nLine 2"#,出来的是一行内容“Line 1\nLine 2”。如果使用#后,又想转义其中的一些特殊字符则用\#符号实现,例如打印#"Line 1\#nLine 2"#,出来的就是两行内容。###"Line1\###nLine2"###具有相同的效果。#符号在多行字符串中的使用例程如下,可以不对#"""所包围的连续三个双引号进行转义。

let threeMoreDoubleQuotationMarks = #"""
Here are three more double quotes: """
"""#

2.2 创建字符串

创建空字符串,使用isEmpty方法判空

var emptyString = ""               // empty string literal
var anotherEmptyString = String()  // initializer syntax
// these two strings are both empty, and are equivalent to each other

if emptyString.isEmpty {
    print("Nothing to see here")
}
// Prints "Nothing to see here"

字符串可编辑性,用varlet修饰符区分,与 Objective-C 不同

var variableString = "Horse"
variableString += " and carriage"
// variableString is now "Horse and carriage"

let constantString = "Highlander"
constantString += " and another Highlander"
// this reports a compile-time error - a constant string cannot be modified

Swift 的 String 是值类型,并按值传递,无论是赋值、用作参数传递,都会拷贝字符串的副本。字符串默认拷贝(copy-by-default)的特点,既可以保证编辑字符串内容时不会影响字符串来源,也可以保证外部对字符串来源的编辑不会影响内部的字符串。

2.3 字符串和字符

for character in "Dog!🐶" {
    print(character)
}
// D
// o
// g
// !
// 🐶

let catCharacters: [Character] = ["C", "a", "t", "!", "🐱"]
let catString = String(catCharacters)
print(catString)
// Prints "Cat!🐱"

2.4 字符串拼接

// 1. 使用+拼接
let string1 = "hello"
let string2 = " there"
var welcome = string1 + string2
// welcome now equals "hello there"

// 2. 使用+=拼接
var instruction = "look over"
instruction += string2
// instruction now equals "look over there"

// 3. 使用append拼接
let exclamationMark: Character = "!"
welcome.append(exclamationMark)
// welcome now equals "hello there!"

// 4. 多行拼接
let badStart = """
one
two
"""
let end = """
three
"""
print(badStart + end)
// Prints two lines:
// one
// twothree

let goodStart = """
one
two

"""
print(goodStart + end)
// Prints three lines:
// one
// two
// three

2.5 字符串插值

// 1. 普通插值
let multiplier = 3
let message = "\(multiplier) times 2.5 is \(Double(multiplier) * 2.5)"
// message is "3 times 2.5 is 7.5"

// 2. 纯字符串插值失败
print(#"Write an interpolated string in Swift using \(multiplier)."#)
// Prints "Write an interpolated string in Swift using \(multiplier)."

// 3. 纯字符串插值成功
print(#"6 times 7 is \#(6 * 7)."#)
// Prints "6 times 7 is 42."

2.6 Unicode

Unicode 是编码、表示、处理文本的跨平台国际标准。可以表示几乎任意语言的字符,并从文件或 web 页面等源中读写字符。Swift 的String对 Unicode 标准有完整的兼容。

Swift 的String是由 Unicode 一个或多个 Unicode 标量值构建而来。Unicode 标量值是 21 位二进制数。每个 Unicode 标量值都会对应一个名称,例如LATIN SMALL LETTER AFRONT-FACING BABY CHICK

Swift 的每个Character实例都代表一个 extended grapheme cluster。Extended grapheme cluster 是一个或多个 Unicode 标量值的串联成的序列,并生成一个 human-readable 的字符。

例如:字母é可以用LATIN SMALL LETTER E WITH ACUTE或者U+00E9表示;也可以用字母eLATIN SMALL LETTER E, or U+0065)和COMBINING ACUTE ACCENTU+0301)表示,后者使 Unicode-aware 文本渲染系统渲染字母e的动作转为渲染é。这两种情况中字母é都是一个字符同时代表了一个 extended grapheme cluster。区别是前者是一个 Unicode 标量值表示,后者是由两个 Unicode 标量值串联成的 extended grapheme cluster 表示。

// 1. Extended grapheme cluster 例子1
let eAcute: Character = "\u{E9}"                         // é
let combinedEAcute: Character = "\u{65}\u{301}"          // e followed by ́
// eAcute is é, combinedEAcute is é

// 2. Extended grapheme cluster 例子2
let precomposed: Character = "\u{D55C}"                  // 한
let decomposed: Character = "\u{1112}\u{1161}\u{11AB}"   // ᄒ, ᅡ, ᆫ
// precomposed is 한, decomposed is 한

// 3. Extended grapheme cluster 例子3
let enclosedEAcute: Character = "\u{E9}\u{20DD}"
// enclosedEAcute is é⃝

// 4. Extended grapheme cluster 例子4
let regionalIndicatorForUS: Character = "\u{1F1FA}\u{1F1F8}"
// regionalIndicatorForUS is 🇺🇸

2.7 字符计数

Swift 字符计数的例子:

let unusualMenagerie = "Koala 🐨, Snail 🐌, Penguin 🐧, Dromedary 🐪"
print("unusualMenagerie has \(unusualMenagerie.count) characters")
// Prints "unusualMenagerie has 40 characters"

由于 Swift 使用 extended grapheme cluster 表示一个字符,因此字符串拼接未必会造成字符串长度变更。例如

var word = "cafe"
print("the number of characters in \(word) is \(word.count)")
// Prints "the number of characters in cafe is 4"

word += "\u{301}"    // COMBINING ACUTE ACCENT, U+0301

print("the number of characters in \(word) is \(word.count)")
// Prints "the number of characters in café is 4"

注意:Extended grapheme clusters 可以由多个 Unicode 标量值组成。也就是说不同的字符串,以及相同字符串的不同表示,都可能造成字符串占用内存空间大小的差异。这导致了Swift 只有遍历这个字符串确定所有 extended grapheme clusters 边界后,才能确定字符串的长度。在处理长字符串时应尤其注意这一点。而且相同的字符串分别用Stringcount属性以及NSStringlength属性计算长度,可能会得到不同的结果。这是因为NSString是根据 UTF-16 编码的字符个数来计算字符串长度,而不是用 extended grapheme clusters。

2.8 访问和修改字符串

Swift 通过index系列方法访问字符串中的字符,接口有很多。StringstartIndexendIndex属性,分别表示字符串的第一个字符和最后一个字符后面的索引,若两者相等则表示空字符串。通过indices可以遍历所有索引。需要注意index必须不能越界。

// 1. 通过 index 访问字符串中的字符
let greeting = "Guten Tag!"
greeting[greeting.startIndex]
// G
greeting[greeting.index(before: greeting.endIndex)]
// !
greeting[greeting.index(after: greeting.startIndex)]
// u
let index = greeting.index(greeting.startIndex, offsetBy: 7)
greeting[index]
// a

// 2. 访问字符串中的字符的错误示范
greeting[greeting.endIndex] // Error,越界
greeting.index(after: greeting.endIndex) // Error,越界

// 3. 遍历字符串中的字符
for index in greeting.indices {
    print("\(greeting[index]) ", terminator: "")
}
// Prints "G u t e n   T a g ! "

插入和删除

// 1. 字符串插入
var welcome = "hello"
welcome.insert("!", at: welcome.endIndex)
// welcome now equals "hello!"

welcome.insert(contentsOf: " there", at: welcome.index(before: welcome.endIndex))
// welcome now equals "hello there!"

// 2. 字符串删除
welcome.remove(at: welcome.index(before: welcome.endIndex))
// welcome now equals "hello there"

let range = welcome.index(welcome.endIndex, offsetBy: -6)..<welcome.endIndex
welcome.removeSubrange(range)
// welcome now equals "hello"

2.9 Substring类型

Substring是 Swift 的一种基本类型。Substring几乎支持String的所有方法,但是Substring只适用于短暂的使用场景,当需要长期保存Substring时,应将其转化为String类型。

let greeting = "Hello, world!"
let index = greeting.firstIndex(of: ",") ?? greeting.endIndex
let beginning = greeting[..<index] // 这里返回了 Substring 类型
// beginning is "Hello"

// Convert the result to a String for long-term storage.
let newString = String(beginning)

SubstringString的区别是,Substring复用了源字符串的部分内存空间或者用于存储其他Substring实例的部分内存空间。因此只要不修改Substring,就不需要考虑Substring的内存拷贝的性能花销问题。Substring之所以只适用于短暂的使用场景,是因为Substring复用了源字符串的内存空间,因此只要Substring还在使用,则源字符串占用的内存会一直得不到释放。下图展示了SubstringString之间的关系。

2.10 字符串比较

Swift 字符串支持运算符比较。

let quotation = "We're a lot alike, you and I."
let sameQuotation = "We're a lot alike, you and I."
if quotation == sameQuotation {
    print("These two strings are considered equal")
}
// Prints "These two strings are considered equal"

需要注意 Swift 字符串判等是根据 extended grapheme clusters,而不是字符串的编码值

// 1. 不同编码值的字符串也可能被判断为相等
// "Voulez-vous un café?" using LATIN SMALL LETTER E WITH ACUTE
let eAcuteQuestion = "Voulez-vous un caf\u{E9}?"

// "Voulez-vous un café?" using LATIN SMALL LETTER E and COMBINING ACUTE ACCENT
let combinedEAcuteQuestion = "Voulez-vous un caf\u{65}\u{301}?"

if eAcuteQuestion == combinedEAcuteQuestion {
    print("These two strings are considered equal")
}
// Prints "These two strings are considered equal"

// 2. 即使两个字符串具有相同的文本渲染结果,但也可能被判断为不相等。
// 下面的例子是:英语中的拉丁字母"A"和俄语中的西里尔字母"A",虽然都显示为"A",但是两者是不相等的
let latinCapitalLetterA: Character = "\u{41}"

let cyrillicCapitalLetterA: Character = "\u{0410}"

if latinCapitalLetterA != cyrillicCapitalLetterA {
    print("These two characters are not equivalent.")
}
// Prints "These two characters are not equivalent."

2.11 前缀和后缀

hasPrefix(_:)hasSuffix(_:)分别判断字符串是否具有某前缀和某后缀。

2.12 字符串的Unicode表示

写入文本时需要指定文本的编码方式,文本编码结果是由一些 code units 串联而成。例如:UTF-8字符串的 code unit 是 8 位二进制数;UTF-16是 16 位二进制数;UTF-32是 32 位二进制数。

Swift 支持几种获取字符串的表示:

  • UTF-8(通过utf8属性获取);
  • UTF-16(通过utf16属性获取);
  • 21 位 Unicode 标量值集合,等价于 UTF-32 编码(通过unicodeScalars属性获取);

注意:Swift 字符串的unicodeScalarsUInt32类型的数组。

// 1. 获取字符串的 UTF-8 编码序列
for codeUnit in dogString.utf16 {
    print("\(codeUnit) ", terminator: "")
}
print("")
// Prints "68 111 103 8252 55357 56374 "

// 2. 获取字符串的 UTF-16 编码序列

// 3. 获取字符串的 21 位 Unicode 标量值集合序列,其中 scalar 是`UnicodeScalar`类型,
for scalar in dogString.unicodeScalars {
    print("\(scalar.value) ", terminator: "")
}
print("")
// Prints "68 111 103 8252 128054 "

// 3. 通过获取字符串的 21 位 Unicode 标量值集合序列遍历字符串的所有字符
for scalar in dogString.unicodeScalars {
    print("\(scalar) ")
}
// D
// o
// g
// ‼
// 🐶

三、集合类型(容器)

Swift 提供了三种基本集合类型:数组、集合、字典。集合是否可修改,取决于集合被声明为var变量还是let常量。若声明为变量则可以修改集合,包括增删改操作;声明为常量则不能进行以上操作。

3.1 数组

Swift 数组的声明语法和 Objective-C 很不一样,使用[${ElementType}]声明数组的类型,完整形态是Array<${ElementType}>,其中${ElementType}是元素类型。数组支持++=运算符拼接数组,十分牛逼。数组扩展了丰富的操作方法,包括countisEmptyinsertremoveappendremoveLast等等。

// 1.声明数组的基本语法
var someInts = [Int]()
print("someInts is of type [Int] with \(someInts.count) items.")
// Prints "someInts is of type [Int] with 0 items."

// 2. 修改数组的语法
someInts.append(3)
// someInts now contains 1 value of type Int
someInts = []
// someInts is now an empty array, but is still of type [Int]

// 3. 使用值创建数组
var threeDoubles = Array(repeating: 0.0, count: 3)
// threeDoubles is of type [Double], and equals [0.0, 0.0, 0.0]

// 4. 用两个数组合成一个数组
var anotherThreeDoubles = Array(repeating: 2.5, count: 3)
// anotherThreeDoubles is of type [Double], and equals [2.5, 2.5, 2.5]

var sixDoubles = threeDoubles + anotherThreeDoubles
// sixDoubles is inferred as [Double], and equals [0.0, 0.0, 0.0, 2.5, 2.5, 2.5]

// 5. 声明数组字面量
var shoppingList: [String] = ["Eggs", "Milk"]
// shoppingList has been initialized with two initial items

// 6. 在类型推断的支持下,声明数组字面量可以省略类型
var shoppingList = ["Eggs", "Milk"]

// 7. 使用索引语法
var firstItem = shoppingList[0]
// firstItem is equal to "Eggs"

shoppingList[0] = "Six eggs"
// the first item in the list is now equal to "Six eggs" rather than "Eggs"

// 7. 高阶索引语法,使用范围运算符,牛逼
shoppingList[4...6] = ["Bananas", "Apples"]

// 8. 数组的遍历
for item in shoppingList {
    print(item)
}

// 9. 数组的遍历,同时获取索引和值,牛逼
for (index, value) in shoppingList.enumerated() {
    print("Item \(index + 1): \(value)")
}

3.2 集合

集合就是哈希表,集合元素类型必须符合Hashable协议。支持countisEmptyinsertremovecontainscount等方法,其中的sorted()方法可以返回对集合元素排序的结果。集合的遍历语法和数组的基本遍历语法一致。

// 1. 生成集合的基本语法
var letters = Set<Character>()
print("letters is of type Set<Character> with \(letters.count) items.")
// Prints "letters is of type Set<Character> with 0 items."

// 2. 声明集合字面量的语法
var favoriteGenres: Set<String> = ["Rock", "Classical", "Hip hop"]

// 3. 集合遍历
for genre in favoriteGenres {
    print("\(genre)")
}
// Classical
// Jazz
// Hip hop

// 4. 有序遍历
for genre in favoriteGenres.sorted() {
    print("\(genre)")
}
// Classical
// Hip hop
// Jazz

集合支持以下几种操作:

  • 交集;
  • 对称差集;
  • 并集;
  • 差集;

let oddDigits: Set = [1, 3, 5, 7, 9]
let evenDigits: Set = [0, 2, 4, 6, 8]
let singleDigitPrimeNumbers: Set = [2, 3, 5, 7]

oddDigits.union(evenDigits).sorted()
// [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
oddDigits.intersection(evenDigits).sorted()
// []
oddDigits.subtracting(singleDigitPrimeNumbers).sorted()
// [1, 9]
oddDigits.symmetricDifference(singleDigitPrimeNumbers).sorted()
// [1, 2, 9]

集合支持以下几种关系判断:

  • 子集;
  • 超集;
  • 无交集;

let houseAnimals: Set = ["🐶", "🐱"]
let farmAnimals: Set = ["🐮", "🐔", "🐑", "🐶", "🐱"]
let cityAnimals: Set = ["🐦", "🐭"]

houseAnimals.isSubset(of: farmAnimals)
// true
farmAnimals.isSuperset(of: houseAnimals)
// true
farmAnimals.isDisjoint(with: cityAnimals)
// true

3.3 字典

字典就是 K-V 形态的哈希表,字典的关键字类型必须符合Hashable协议。字典类型用[Key: Value]表示,完全形态是Dictionary<Key: Value>。字典字面量的表示和 Objective-C 不太一样,使用[]符号包围。字典修改元素的方法是updateValue(_:, forKey:)方法,移除元素用removeValue(forKey:)方法。字典也支持索引访问。

// 1. 声明空字典
var namesOfIntegers = [Int: String]()
// namesOfIntegers is an empty [Int: String] dictionary

// 2. 字典也支持索引
namesOfIntegers[16] = "sixteen"
// namesOfIntegers now contains 1 key-value pair
namesOfIntegers = [:]
// namesOfIntegers is once again an empty dictionary of type [Int: String]

// 3. 声明字典字面量
var airports: [String: String] = ["YYZ": "Toronto Pearson", "DUB": "Dublin"]
// 类型推断
var airports = ["YYZ": "Toronto Pearson", "DUB": "Dublin"]

// 4. 字典的遍历语法比较灵活,下面列出三种:遍历Key和Value、只遍历Key、只遍历Value
for (airportCode, airportName) in airports {
    print("\(airportCode): \(airportName)")
}
// LHR: London Heathrow
// YYZ: Toronto Pearson

for airportCode in airports.keys {
    print("Airport code: \(airportCode)")
}
// Airport code: LHR
// Airport code: YYZ

for airportName in airports.values {
    print("Airport name: \(airportName)")
}
// Airport name: London Heathrow
// Airport name: Toronto Pearson

四、控制流

Swift 支持一系列控制流语句,包括whilerepeat-whilefor-in循环语句,ifguardswitch判断语句,breakcontinue控制语句。

4.1 for-in循环

直接上代码。

// 1. for-in遍历数组
let names = ["Anna", "Alex", "Brian", "Jack"]
for name in names {
    print("Hello, \(name)!")
}
// Hello, Anna!
// Hello, Alex!
// Hello, Brian!
// Hello, Jack!

// 2. or-in遍历字典
let numberOfLegs = ["spider": 8, "ant": 6, "cat": 4]
for (animalName, legCount) in numberOfLegs {
    print("\(animalName)s have \(legCount) legs")
}
// cats have 4 legs
// ants have 6 legs
// spiders have 8 legs

// 3. 使用范围运算符
for index in 1...5 {
    print("\(index) times 5 is \(index * 5)")
}
// 1 times 5 is 5
// 2 times 5 is 10
// 3 times 5 is 15
// 4 times 5 is 20
// 5 times 5 is 25

// 3. 使用范围运算符,包括闭区间和半开区间
let base = 3
let power = 10
var answer = 1
for _ in 1...power {
    answer *= base
}
print("\(base) to the power of \(power) is \(answer)")
// Prints "3 to the power of 10 is 59049"

let minutes = 60
for tickMark in 0..<minutes {
    // render the tick mark each minute (60 times)
}

// 4. 使用stride系列函数,生成步长数组
let minuteInterval = 5
for tickMark in stride(from: 0, to: minutes, by: minuteInterval) {
    // render the tick mark every 5 minutes (0, 5, 10, 15 ... 45, 50, 55)
}

let hours = 12
let hourInterval = 3
for tickMark in stride(from: 3, through: hours, by: hourInterval) {
    // render the tick mark every 3 hours (3, 6, 9, 12)
}

4.2 while循环

While 循环有两种:while 和 repeat-while 语法如下

// 1. while语法
while condition {
    statements
}

// 2. repeat-while语法
repeat {
    statements
} while condition

4.3 条件语句

Swift 的if语句没什么特别,除了判断式不需要()包围。

var temperatureInFahrenheit = 30
if temperatureInFahrenheit <= 32 {
    print("It's very cold. Consider wearing a scarf.")
}
// Prints "It's very cold. Consider wearing a scarf."

但是 Swift 的swift语言就强大的一批。基本语法如下,省略了啰嗦的break语句,代码得到了肉眼可见的简化。但是同时也不允许空的case体,否则提示编译错误,又引进了更精简的匹配多个case的语法。Swift 的switch的强大有以下几个方面:

  • 支持整型数、浮点数、字符、字符串、以及这些类型的联合体的匹配;
  • 缺省break语句;
  • 支持多项匹配;
  • 支持范围表达式匹配;
  • 支持where修饰语句;
  • 支持值动态绑定;
// 1. switch 缺省 break 语句
let someCharacter: Character = "z"
switch someCharacter {
case "a":
    print("The first letter of the alphabet")
case "z":
    print("The last letter of the alphabet")
default:
    print("Some other character")
}
// Prints "The last letter of the alphabet"

// 2. 不允许空 case 体
let anotherCharacter: Character = "a"
switch anotherCharacter {
case "a": // Invalid, the case has an empty body
case "A":
    print("The letter A")
default:
    print("Not the letter A")
}
// This will report a compile-time error.

// 3. 多项 case 匹配语法
let anotherCharacter: Character = "a"
switch anotherCharacter {
case "a", "A":
    print("The letter A")
default:
    print("Not the letter A")
}
// Prints "The letter A"

// 4. 支持范围表达式匹配
let approximateCount = 62
let countedThings = "moons orbiting Saturn"
let naturalCount: String
switch approximateCount {
case 0:
    naturalCount = "no"
case 1..<5:
    naturalCount = "a few"
case 5..<12:
    naturalCount = "several"
case 12..<100:
    naturalCount = "dozens of"
case 100..<1000:
    naturalCount = "hundreds of"
default:
    naturalCount = "many"
}
print("There are \(naturalCount) \(countedThings).")
// Prints "There are dozens of moons orbiting Saturn."

// 5. 支持按元组匹配
let somePoint = (1, 1)
switch somePoint {
case (0, 0):
    print("\(somePoint) is at the origin")
case (_, 0):
    print("\(somePoint) is on the x-axis")
case (0, _):
    print("\(somePoint) is on the y-axis")
case (-2...2, -2...2):
    print("\(somePoint) is inside the box")
default:
    print("\(somePoint) is outside of the box")
}
// Prints "(1, 1) is inside the box"


// 6. 支持临时值绑定
let anotherPoint = (2, 0)
switch anotherPoint {
case (let x, 0):
    print("on the x-axis with an x value of \(x)")
case (0, let y):
    print("on the y-axis with a y value of \(y)")
case let (x, y):
    print("somewhere else at (\(x), \(y))")
}
// Prints "on the x-axis with an x value of 2"

// 7. 支持 where 修饰语句
let yetAnotherPoint = (1, -1)
switch yetAnotherPoint {
case let (x, y) where x == y:
    print("(\(x), \(y)) is on the line x == y")
case let (x, y) where x == -y:
    print("(\(x), \(y)) is on the line x == -y")
case let (x, y):
    print("(\(x), \(y)) is just some arbitrary point")
}
// Prints "(1, -1) is on the line x == -y"

// 8. 支持值、支持元组多项匹配
let someCharacter: Character = "e"
switch someCharacter {
case "a", "e", "i", "o", "u":
    print("\(someCharacter) is a vowel")
case "b", "c", "d", "f", "g", "h", "j", "k", "l", "m",
     "n", "p", "q", "r", "s", "t", "v", "w", "x", "y", "z":
    print("\(someCharacter) is a consonant")
default:
    print("\(someCharacter) is not a vowel or a consonant")
}
// Prints "e is a vowel"

let stillAnotherPoint = (9, 0)
switch stillAnotherPoint {
case (let distance, 0), (0, let distance):
    print("On an axis, \(distance) from the origin")
default:
    print("Not on an axis")
}
// Prints "On an axis, 9 from the origin"

4.4 控制变换语句

Swift 支持五种控制变换语句:

  • continue:用于循环控制,直接进入下一次循环迭代;
  • break:用于循环控制和switch条件判断控制,直接跳出当前循环迭代,或直接跳出case匹配;
  • fallthrough:用于switch条件判断控制,进入下一个case匹配;
  • return:用于函数返回;
  • throw:用于错误处理;
// 1. continue 语句
let puzzleInput = "great minds think alike"
var puzzleOutput = ""
let charactersToRemove: [Character] = ["a", "e", "i", "o", "u", " "]
for character in puzzleInput {
    if charactersToRemove.contains(character) {
        continue
    }
    puzzleOutput.append(character)
}
print(puzzleOutput)
// Prints "grtmndsthnklk"

// 2. break 语句
let numberSymbol: Character = "三"  // Chinese symbol for the number 3
var possibleIntegerValue: Int?
switch numberSymbol {
case "1", "١", "一", "๑":
    possibleIntegerValue = 1
case "2", "٢", "二", "๒":
    possibleIntegerValue = 2
case "3", "٣", "三", "๓":
    possibleIntegerValue = 3
case "4", "٤", "四", "๔":
    possibleIntegerValue = 4
default:
    break
}
if let integerValue = possibleIntegerValue {
    print("The integer value of \(numberSymbol) is \(integerValue).")
} else {
    print("An integer value could not be found for \(numberSymbol).")
}
// Prints "The integer value of 三 is 3."

// 3. fallthrough 语句
let integerToDescribe = 5
var description = "The number \(integerToDescribe) is"
switch integerToDescribe {
case 2, 3, 5, 7, 11, 13, 17, 19:
    description += " a prime number, and also"
    fallthrough
default:
    description += " an integer."
}
print(description)
// Prints "The number 5 is a prime number, and also an integer."

Swift 的while循环有种比较特殊的特性,可以命名循环,使用breakcontinue进行循环控制时,指定循环名,可以指定该语句具体控制的循环。

gameLoop: while square != finalSquare {
    diceRoll += 1
    if diceRoll == 7 { diceRoll = 1 }
    switch square + diceRoll {
    case finalSquare:
        // diceRoll will move us to the final square, so the game is over
        break gameLoop
    case let newSquare where newSquare > finalSquare:
        // diceRoll will move us beyond the final square, so roll again
        continue gameLoop
    default:
        // this is a valid move, so find out its effect
        square += diceRoll
        square += board[square]
    }
}
print("Game over!")

4.5 卫语句

卫语句guard类似于if,是条件判断控制语法。其特别之处在于其判断式对应的{}主体为空。当用if判断一个条件时,若该逻辑分支为空,则此时不得不将条件改为否定式,然而否定式又会影响代码可读性,因此gurad应运而生。guard语句是指判断式if分支为空,else分支存在处理逻辑。这种语法叫做 early exist。使用 early exist 有利于提高代码可读性。

func greet(person: [String: String]) {
    guard let name = person["name"] else {
        return
    }

    print("Hello \(name)!")

    guard let location = person["location"] else {
        print("I hope the weather is nice near you.")
        return
    }

    print("I hope the weather is nice in \(location).")
}

greet(person: ["name": "John"])
// Prints "Hello John!"
// Prints "I hope the weather is nice near you."
greet(person: ["name": "Jane", "location": "Cupertino"])
// Prints "Hello Jane!"
// Prints "I hope the weather is nice in Cupertino."

4.6 检查API是否可用

if #available(iOS 10, macOS 10.12, *) {
    // Use iOS 10 APIs on iOS, and use macOS 10.12 APIs on macOS
} else {
    // Fall back to earlier iOS and macOS APIs
}

五、总结

  • Swift 支持给可空类型指定默认值的空判断运算符??
  • Swift 类型支持逻辑运算符重载;
  • Swift 支持范围运算符和步长函数stride,范围运算符包括闭区间运算符...和半开区间范围运算符..<
  • Swift 的多行字符串字面量使用"""符号指定可以支持强大的功能,例如缩进量,换行等;
  • 给字符串运算符添加#前缀表示忽略字符串中的转义字符;
  • 字符串支持++=运算符拼接;
  • 字符串格式化输出语法十分简练,称为字符串插值,使用\()符号插入变量;
  • Swift 的字符串是根据 extended grapheme clusters 切分字符,extended grapheme clusters 是一个或多个 21 位二进制的 Unicode 编码标量值(在 Swift 中则用 32 位的Int类型值表示)的串联,串联可以形成字符。字符的 extended grapheme clusters 表示可能有多种,因此打印出来内容相同的字符串未必占用相同大小的内存空间,拼接字符串也未必会造成字符串长度改变;
  • Substring子串并不等同与字符串,虽然使用起来基本相同,区别在于多个Subtring的拷贝可能会共享同一个源String的一块内存空间,因此不使用于字符串生命周期拉得比较长的场景,这种场景应该将Subtring转化为String保存;
  • Swift 的集合类型的特征和 Objective-C 差不多,需要注意声明字典字面量时使用[]符号包围,数组可以用范围表达式取多个元素,不知道支不支持stride按步长取元素,可以试一试;
  • Swift 的switch语句异常强大:
    • 支持整型数、浮点数、字符、字符串、以及这些类型的联合体的匹配;
    • 缺省break语句;
    • 支持多项匹配;
    • 支持范围表达式匹配;
    • 支持where修饰语句;
    • 支持值动态绑定;
  • Swift 支持guard卫语句,用于快速返回场景,增强代码可读性;
  • Swift 支持对while循环命名,可以指定breakcontinue具体控制哪个循环;