Swift控制流

目录

引入

前面学习Swift编写的代码, 都是从上向下依次执行。 我们做不到选择某些语句执行, 或者根据条件选择是否执行; 或者是学习集合类型的时候, 也没有办法很方便的, 将集合中所有的值都获取使用。要想实现这些目标,就离不开控制流。

For-In循环

使用for-in循环可以遍历一个序列。

遍历数组

let numbers = [3, 5, 7, 4, 2]
for item in numbers {
    print(item)
}

运行结果

3 5 7 4 2

如果遍历数组的同时还需要当前值对应的索引, 可以使用 enumerated 方法。 enumerated 方法会返回一个由 整数索引和值构成的元组。

let numbers = [3, 5, 7, 4, 2]
for (index, value) in numbers.enumerated() {
    print("索引:\(index),值:\(value)")
}

运行结果

索引:0, 值:3 索引:1, 值:5 索引:2, 值:7 索引:3, 值:4 索引:4, 值:2

遍历集合

let ids: Set = [10, 20, 30, 40, 50]
for id in ids {
    print(id)
}

运行结果

30 10 40 50 20

注意集合是无序的。 如果需要按照特定顺序遍历集合, 可以使用 sorted 方法。

let ids: Set = [10, 20, 30, 40, 50]
for id in ids.sorted() {
    print(id)
}

运行结果

10 20 30 40 50

遍历字典

遍历字典时,每次循环的是字典中的键值对。

let users = ["王力宏": 49, "陶喆": 54, "周杰伦": 46]
for (name, age) in users {
    print("姓名:\(name), 年龄:\(age)")
}

运行结果

姓名:王力宏, 年龄:49 姓名:周杰伦, 年龄:46 姓名:陶喆, 年龄:54

遍历字典所有的键或值

如果只需要遍历字典中的键可以使用 keys 属性, 只要值使用 values 属性。

let users = ["王力宏": 49, "陶喆": 54, "周杰伦": 46]

print(users.keys)

for key in users.keys {
    print(key)
}

print(users.values)

for value in users.values {
    print(value)
}

运行结果

[“周杰伦”, “王力宏”, “陶喆”] 周杰伦 王力宏 陶喆

[46, 49, 54] 46 49 54

遍历字符串

使用for-in遍历字符串, 可以访问字符串的每个字符值。

let str = "hello world"
for letter in str {
    print(letter)
}

运行结果

h e l l o


w o r l d

范围运算符

封闭范围运算符

a...b 运算符定义了a到b的范围, 其中包括a和b的值。 在对范围遍历时, 希望使用其中的所有值,可以使用for-in循环。

for item in 1...5 {
    print(item)
}

运行结果

1 2 3 4 5

半开放范围运算符

a..<b 运算符定义了a到b的范围, 不包括b的值。 如果a的值等于b, 范围将是空的。

for item in 1..<5 {
    print(item)
}

运行结果

1 2 3 4

单边范围

封闭范围运算符有另一种形式, 可用于向一个方向延伸的范围。

比如需要数组索引2以后的所有元素, 可以表示为 [2...]

let letters = ["a", "b", "c", "d", "e"]
for letter in letters[2...] { // 获取letters数组索引2及以后的值
    print(letter)
}

print(" ")

for letter in letters[...3] { // 获取开始到索引3的值
    print(letter)
}

print(" ")

for letter in letters[..<3] { // 获取开始到索引3的值, 不包括索引3
    print(letter)
}

运行结果

c d e


a b c d


a b c

使用范围运算符循环指定次数

有了范围运算符, 想要循环固定次数就非常容易了。

let count = 10
for _ in 0..<count { // 既然固定次数,范围的值也就不用了,可以使用_
    print("hello")
}

运行结果

hello hello hello hello hello hello hello hello hello hello

stride函数

某些场景下, 范围运算符也不够用。 比如需要以5为步进的值序列:5, 10, 15, 20… 。 我们可以使用stride函数。

// stride(from: 起始, to: 结束, by: 步进),不包括结束
for item in stride(from: 5, to: 30, by: 5) {
    print(item)
}

print("")

// stride(from: 起始, through: 结束, by: 步进),包括结束
for item in stride(from: 5, through: 30, by: 5) {
    print(item)
}

运行结果

5 10 15 20 25


5 10 15 20 25 30

While循环

while循环执行一组语句, 直到条件变为false。 如果不知道迭代的次数, 使用while循环就很合适。

Swift提供了两种while循环:

  • while在每次循环开始时检查条件
  • repeat-while在每次循环结束时检查条件

while

while循环以检查条件开始, 如果条件为真, 就执行一组语句; 如果条件为假, 就退出循环, 向下执行其他代码。

var start = 0 // 起始值
var total = 0 // 累加结果
let end = 100 // 终止值

while start <= end { // 当起始值小于等于终止值时,就执行话括号中的语句
    total += start // 将当前的值累加到total上
    start += 1 // 让当前的值自增1
}

print(total) // 输出结果,5050

Repeat-While

在检查循环条件之前, 先执行一次循环语句。 然后重复循环, 直到条件变为false。

// 使用repeat-while循环输出5,4,3,2,1
var number = 5

repeat {
    print(number) // 输出当前值
    number -= 1 // 将number减1
} while number > 0 // 检查条件,number>0就继续,否则退出循环

运行结果

5 4 3 2 1

条件语句

根据特定条件执行不同的代码。

If

单个if条件

最简单的语句就是只有一个if条件。 只有条件为真时, 才会执行一组语句。

print("begin")
let number = 5
if number > 0 {
    print("bigger") // 当number>0条件为真时,才会执行这段代码
}
print("end")

运行结果

begin bigger end

尝试把number设置成负数, 看看结果。

if-else

如果根据条件成立与否,需要执行不同代码块, 可以使用if - else。

let isRainyDay = false
if isRainyDay {
    print("正在下雨,请打伞") // isRainyDay为真执行
} else {
    print("好天气,随便玩") // isRainyDay为假执行
}

运行结果

好天气,随便玩

if - else if - else

如果条件判断的结果不只两种, 可以通过 else if 加入更多分支。

let weather = "snowy" // weather记录当前天气

if weather == "sunny" {         // if语句
    print("大晴天,出去玩")
} else if weather == "rainy" {  // 执行语句后面使用else if加入新分支
    print("下雨天,慢慢玩")
} else if weather == "snowy" {  // 执行语句后面使用else if加入新分支
    print("下雪天,小心玩")
} else {                        // 上面的条件都不满足,就执行这里
    print("未知天,不去玩")
}

运行结果

下雪天,小心玩

💡

在多分支表达式中, 从上往下检查条件, 某个条件为真, 这个分支的值就会作为整个if表达式的值。

将if表达式的结果赋值给变量/常量

如果if表达式的结果不是输出, 而是需要保存到变量/常量中使用时, 也是可以的。

let age = 18

let isAdult = if age >= 18 {
    true // 如果age大于等于18,就给isAdult赋值为true
} else {
    false // 否则,赋值为false
}

print(isAdult) // true

如果不同分支返回的数据类型不同, 可以提供显式类型, 或者使用 as 做类型转换。

let age = 18

let isAdult: Bool? = if age >= 18 { // 给isAdult显式类型
    true
} else {
    nil // 不满足就返回nil
}

print(isAdult) // Optional(true)
let age = 18

let isAdult = if age >= 18 {
    true
} else {
    nil as Bool? // as类型转换
}

print(isAdult) // Optional(true)

可选绑定

使用可选绑定查找一个可选项是否包含值。 如果有值, 就将该值作为一个临时变量/常量使用。

let str = "123"

if let number = Int(str) {
    print("访问转换成功的数字 \(number)")
} else {
    print("看来转不出来")
}

运行结果

访问转换成功的数字 123

如果访问可选常量/变量包含的值以后, 不再需要原来的可选常量/变量, 可以使用相同名称。

let origin = "123"
let number = Int(origin)

if let number = number { // 新的名称和原来的名称一样
    print(number)
}

if let number { // 上面这样的代码很常见,所以可以简写为这样
    print(number)
}

运行结果

123 123

三元条件运算符

根据条件表达式的结果, 返回不同的值是一种很常见的场景。比如上面出现过的例子:

let age = 16

let isAdult = if age >= 18 {
    true
} else {
    false
}

print(isAdult)  // false

正因为很常见, 所以有一个特殊的运算符处理这种情况。

三元条件运算符 条件 ? 表达式1 : 表达式2 。 如果条件为真, 对表达式1求值并返回结果, 否则对表达式2求值并返回结果。

上面的代码可以简写成这样:

let age = 16

let isAudlt = age >= 18 ? true : false

print(isAudlt) // false

Switch

switch语句会检查一个值, 并将其与可能匹配的模式进行比较, 找到第一个成功匹配的模式执行其代码块。

每个switch语句由多个可能的case组成, 每个case由case关键字开头。 case就是代码执行的一个单独分支。

switch语句应当是具有穷举性的, 即每一种可能性都要考虑到。 如果要涵盖不明确的值, 可以使用default关键字 表示默认情况。 default语句应该在switch的最后。

let weather = "sunny"

switch weather {
case "rainy":
    print("雨天")
case "sunny":
    print("晴天")
case "snowy":
    print("雪天")
default:
    print("未知")
}

运行结果

晴天

表达式形式

let weather = "sunny"

let messsage = switch weather {
case "rainy":
    "雨天"
case "sunny":
    "晴天"
case "snowy":
    "雪天"
default:
    "未知"
}

print(messsage)  // 晴天

多个模式执行相同语句

如果多个case匹配后执行的语句相同, 可以放在一个case中, 使用逗号分隔。

let letter = "a"

let messsage = switch letter {
case "a", "A": // a 和 A 都会匹配到当前模式
    "这是字母a或者A"
default:
    "不是字母a或者A"
}

print(messsage)  // 这是字母a或者A

区间匹配

可以检查switch的值是否包含在区间中。

let score = 85

let result = switch score {
case 90...100:
    "best"
case 80..<90:
    "good"
case 70..<80:
    "fine"
case 60..<70:
    "just so so"
default:
    "bad"
}

print(result) // good

控制转移

控制转移语句通过将控制权从一段代码转移到另一段代码, 来改变代码的执行顺序。

Continue

continue语句停止当前循环, 立即开始下一次循环。

let singers = ["陶喆", "王力宏", "周杰伦", "林俊杰"]

// 遍历singers,播放每个人的歌曲,如果碰到周杰伦的歌,就跳过不放。
for singer in singers {
    if singer == "周杰伦" {
        continue; // 如果是周杰伦,立即停止,开始下一次循环。
    }
    print("播放\(singer)的歌曲.")
}

运行结果

播放陶喆的歌曲. 播放王力宏的歌曲. 播放林俊杰的歌曲.

Break

break语句立即结束整个控制流语句的执行。 可以用于退出循环语句和Switch语句。

let singers = ["陶喆", "王力宏", "周杰伦", "林俊杰"]

for singer in singers {
    if singer == "王力宏" {
        break; // 如果播放到王力宏的歌,就立即退出整个循环
    }
    print("播放\(singer)的歌曲.")
}

运行结果

播放陶喆的歌曲.

Fallthrough

在swift中, 当switch匹配到case以后, 会返回匹配结果。 如果需要匹配成功以后, 继续向下匹配, 可以使用 fallthrough 关键字。

let weather = "sunny"

switch weather {
case "rainy": // 不能匹配
    print("雨天")
case "sunny": // 匹配成功
    print("晴天") // 输出晴天
    fallthrough // 因为fallthrough语句,继续执行下一个case
case "snowy":
    print("雪天") // 输出雪天,没有fallthrough,退出switch
default:
    print("未知")
} // 晴天

运行结果

晴天 雪天

标签语句

真实业务中, 会出现许多复杂的控制流, 让循环和条件相互嵌套。

如果存在嵌套, break 和 continue 会在当前结构块中生效。 如果想要操作的是外部循环和条件, 可以加上标签。

比如下面的九九乘法表

for i in 1...9 {
    for j in 1...i {
        // print函数的terminator设置输出以后的终止符,默认是\n换行
        print("\(j) * \(i) = \(j * i)", terminator: " ")
    }
    print("")
}

运行结果

1 * 1 = 1 1 * 2 = 2 2 * 2 = 4 1 * 3 = 3 2 * 3 = 6 3 * 3 = 9 1 * 4 = 4 2 * 4 = 8 3 * 4 = 12 4 * 4 = 16 1 * 5 = 5 2 * 5 = 10 3 * 5 = 15 4 * 5 = 20 5 * 5 = 25 1 * 6 = 6 2 * 6 = 12 3 * 6 = 18 4 * 6 = 24 5 * 6 = 30 6 * 6 = 36 1 * 7 = 7 2 * 7 = 14 3 * 7 = 21 4 * 7 = 28 5 * 7 = 35 6 * 7 = 42 7 * 7 = 49 1 * 8 = 8 2 * 8 = 16 3 * 8 = 24 4 * 8 = 32 5 * 8 = 40 6 * 8 = 48 7 * 8 = 56 8 * 8 = 64 1 * 9 = 9 2 * 9 = 18 3 * 9 = 27 4 * 9 = 36 5 * 9 = 45 6 * 9 = 54 7 * 9 = 63 8 * 9 = 72 9 * 9 = 81

我希望当j等于5的时候, 停止外部当前循环,并立即开始下一次。 这可以通过加上标签实现。

outer: for i in 1...9 {
    for j in 1...i {
        if j == 5 {
            continue outer
        }
        print("\(j) * \(i) = \(j * i)", terminator: " ")
    }
    print("")
}

运行结果

1 * 1 = 1 1 * 2 = 2 2 * 2 = 4 1 * 3 = 3 2 * 3 = 6 3 * 3 = 9 1 * 4 = 4 2 * 4 = 8 3 * 4 = 12 4 * 4 = 16 1 * 5 = 5 2 * 5 = 10 3 * 5 = 15 4 * 5 = 20 1 * 6 = 6 2 * 6 = 12 3 * 6 = 18 4 * 6 = 24 1 * 7 = 7 2 * 7 = 14 3 * 7 = 21 4 * 7 = 28 1 * 8 = 8 2 * 8 = 16 3 * 8 = 24 4 * 8 = 32 1 * 9 = 9 2 * 9 = 18 3 * 9 = 27 4 * 9 = 36

延迟执行

可以使用 defer 推迟代码块的执行。 无论程序如果退出该作用域, defer内的代码始终会执行。 所以defer非常适合延缓必要操作的执行。 比如开始和结束事务, 打开和关闭文件等。

let filePath = "test.txt"

if filePath != "" {
    print("打开文件")
    defer {
        print("关闭文件")
    }
    print("读取文件");
}

运行结果

打开文件 读取文件 关闭文件

如果存在多个defer代码块, 第一个defer将最后执行。

let filePath = "test.txt"

if filePath != "" {
    print("打开文件")
    defer {
        print("操作完成")
    }
    defer {
        print("关闭文件")
    }
    print("读取文件");
}

运行结果

打开文件 读取文件 关闭文件 操作完成

检查API可用性

Swift内置检查API可用性的支持, 确保不会在部署目标上使用不可用的API。

// 检查是否是iOS 15,macOS 14以上的版本
// 最后的*必需, 表示其他平台以最小部署目标执行
if #available(iOS 15, macOS 14, *) {
    print("使用xxxAPI")
} else {
    print("想想其他办法")
}

运行结果

使用xxxAPI

在使用带有保护语句的可用性条件时, 会完善该代码块中其他代码所使用的可用性信息。

@available(macOS 10.12, *)
struct ColorPreference {
    var bestColor = "blue"
}


func chooseBestColor() -> String {
    guard #available(macOS 10.12, *) else {
       return "gray"
    }
    let colors = ColorPreference()
    return colors.bestColor
}

下一章

控制流语句可以帮助我们实现复杂逻辑, 所有的应用都离不开它们。

下一章, 我们看看Swift中的函数是怎样的。