流程控制

循环控制

for-in 循环

1、使用 for-in 循环来遍历序列,比如一个范围的数字:

1
2
3
for i in 0...5 {
print(i)
}

打印结果:

1
2
3
4
5
6
0
1
2
3
4
5

2、遍历字符串中的字符:

1
2
3
for c in "Hello,World" {
print(c)
}

打印结果:

1
2
3
4
5
6
7
8
9
10
h
e
l
l
o
,
W
o
r
l

3、遍历数组中的元素:

1
2
3
4
let names = ["zhangsan", "lisi", "wangwu", "zhaoliu"]
for name in names {
print(name)
}

打印结果:

1
2
3
4
zhangsan
lisi
wangwu
zhaoliu

for-in 遍历字典

1、字典在 for-in 遍历时,每一个元素都是一个元组 (key, value)

1
2
3
4
let numberLegs = ["spider": 8, "ant": 6, "cat": 4]
for t in numberLegs {
print("\(t.0) has \(t.1) legs")
}

打印结果:

1
2
3
spider has 8 legs
cat has 4 legs
ant has 6 legs

2、可以在 for-in 循环体中,使用显示命名常量来分解元组成员

1
2
3
4
let numberLegs = ["spider": 8, "ant": 6, "cat": 4]
for (animalName, legCount) in numberLegs {
print("\(animalName) has \(legCount) legs")
}

打印结果:

1
2
3
ant has 6 legs
cat has 4 legs
spider has 8 legs

3、在不需要遍历到的每一个值时,可以使用下划线 _ 取代遍历名以忽略值:

1
2
3
4
5
6
7
8
9
let base = 3
let power = 5
var answer = 1
for _ in 1...power {
answer *= base
}
print("\(base) to the power of \(power) is \(answer)")
// Prints "3 to the power of 5 is 243"

for-in 分段区间

使用 stride() 函数来跳过不想要的标记。

  • stride(from:to:by:):开区间
1
2
3
4
let minuteInterval = 5
for tickMark in stride(from: 0, to: 50, by: minuteInterval) {
print(tickMark)
}

打印结果:

1
2
3
4
5
6
7
8
9
10
0
5
10
15
20
25
30
35
40
45
  • stride(from:through:by:):闭区间
1
2
3
4
let minuteInterval = 5
for tickMark in stride(from: 0, through: 50, by: minuteInterval) {
print(tickMark)
}

打印结果:

1
2
3
4
5
6
7
8
9
10
11
0
5
10
15
20
25
30
35
40
45
50

while 循环

Swift 中的 repeat-while 循环。

1
2
3
4
5
var count = 0
repeat {
print(count)
count += 1
} while count < 5

打印结果:

1
2
3
4
5
0
1
2
3
4

OC 中的 do-while 循环:

1
2
3
4
5
int count = 0;
do {
NSLog(@"%d", count);
count += 1;
} while (count < 5);

打印结果:

1
2
3
4
5
0
1
2
3
4

OC 中的 while 循环

1
2
3
4
5
int count = 0;
while (count < 5) {
NSLog(@"%d", count);
count += 1;
}

打印结果:

1
2
3
4
5
0
1
2
3
4

switch

switch 语句会将一个值与多个可能的模式匹配,然后基于第一个成功匹配的模式来执行合适的代码块。

switch 语句一定得是全面的,在给定类型里,每一个值都要被考虑到,并且匹配到一个对应的 switch case 里。如果无法提供一个 switch case 所有可能的值,则可以用关键字 default 标记,即默认匹配所有未明确的值。default 关键字必须在所有 case 的最后。

1
2
3
4
5
6
7
8
9
10
11
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"

如果没有 default 关键字,则会报错:
01

Objective-C 中的 switch 语句如果不全面,仍然可以运行

1
2
3
4
5
6
7
8
9
10
11
char c = 'z';
switch (c) {
case 'a':
NSLog(@"The first letter of the alphabet");
break;
case 'z':
NSLog(@"The last letter of the alphabet");
break;
}
// Prints "The last letter of the alphabet"

没有隐式贯穿

相比 C 和 Objective-C 里的 switch 语句来说,Swift 里的 switch 语句不会默认从匹配 case 的末尾贯穿到下一个 case 里。相反,整个 switch 语句会在匹配到第一个 switch 的 case 执行完毕之后退出,不再需要显式的 break 语句。

C 和 Objective-C 里的 switch 的隐式贯穿:

1
2
3
4
5
6
7
8
9
10
11
12
char c = 'z';
switch (c) {
case 'a':
NSLog(@"The first letter of the alphabet");
case 'z':
NSLog(@"The last letter of the alphabet");
default:
NSLog(@"Some other character");
}
// The last letter of the alphabet
// Some other character

因为 Swift 的 switch 语句没有隐式贯穿,所以每一个 case 的函数体必须包含至少一个可执行的语句。

02

因为 Swift 的 switch 语句没有隐式贯穿,所以想要实现匹配多个值的情况,可以用逗号分隔,并且可以写成多行。

1
2
3
4
5
6
7
8
9
10
11
12
let someCharacter: Character = "z"
switch someCharacter {
case "a", "A", "B",
"C", "D":
print("The first letter of the alphabet")
case "z", "Z":
print("The last letter of the alphabet")
default:
print("Some other character")
}
// Prints "The last letter of the alphabet"

区间匹配

switch 的 case 的值可以在一个区间中匹配。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
let approximateCount = 62
let countedThings = "moons orbiting Saturn"
var 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."

元组匹配

可以使用元组来在一个 switch 语句中测试多个值,使用下划线 _ 来表明匹配所有可能的值。

匹配在 (-2, 2) 组成的盒子内的点:

03

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
let somePoint = (1, 1)
switch somePoint {
case (0, 0):
print("(0, 0) is at the origin")
case (_, 0):
print("(\(somePoint.0), 0) is on the x-axis")
case (0, _):
print("(0, \(somePoint.1)) is on the y-axis")
case (-2...2, -2...2):
print("(\(somePoint.0), \(somePoint.1)) is inside the box")
default:
print("(\(somePoint.0), \(somePoint.1)) is outside of the box")
}
// Prints "(1, 1) is inside the box"

值绑定

switch 的 case 可以将匹配到的值临时绑定为一个常量或者变量,来给 case 的函数体使用。

1
2
3
4
5
6
7
8
9
10
11
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("ont 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"

如果使用 var 关键字,临时的变量就会以合适的值来创建并初始化。这个变量的任何改变都只会在 case 的函数体内有效。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
let anotherPoint = (2, 0)
switch anotherPoint {
case (var x, 0):
x += 2
print("on the x-axis with an x value of \(x)")
case (0, var y):
print("on the y-axis with a y value of \(y)")
case var (x, y):
print("somewhere else at (\(x), \(y))")
}
print(anotherPoint)
// on the x-axis with an x value of 4
// (2, 0)

where 分句

switch case 可以使用 where 分句来检查是否符合特定的约束。

匹配在两条直线上的点:
04

1
2
3
4
5
6
7
8
9
10
11
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"

复合匹配

多种情形共享同一个函数体的多个情况可以在 case 后写多个模式来复合,在每个模式之间用逗号分隔。如果任何一个模式匹配了,那么这个情况都会被认为是匹配的。如果模式太长,可以把它们写成多行。

1
2
3
4
5
6
7
8
9
10
11
12
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"

复合匹配 - 值绑定

复合匹配同样可以包含值绑定。所有复合匹配的模式都必须包含相同的值绑定集合,并且复合情况中的每一个绑定都得有相同的类型格式。这才能确保无论复合匹配的哪部分命中了,在函数体中的代码都能访问到绑定的值,并且值的类型也都相同。

1
2
3
4
5
6
7
8
9
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"

控制转移

continue

continue 语句表示让循环停止当前遍历,开始下一次遍历。并不是离开整个循环。

1
2
3
4
5
6
for i in 0...5 {
if i == 3 {
continue
}
print(i)
}

打印结果:

1
2
3
4
5
0
1
2
4
5

break

break 语句会立即结束整个控制流语句。在需要提前结束 switch 或者其它循环语句时,可以使用 break。

break 会立即结束整个循环的执行,并且转移控制到循环结束花括号(})后的第一行代码上。结束整个循环,表示当前遍历循环里的其它代码都不会被执行,并且余下的遍历循环也不会开始了。

1
2
3
4
5
6
for i in 0...5 {
if i == 3 {
break
}
print(i)
}

打印结果:

1
2
3
0
1
2

switch 语句里使用 break 时,switch 语句会立即结束执行,并且转移控制到 switch 语句结束花括号(})之后的第一行代码上。

1
2
3
4
5
6
7
8
9
10
11
let string = "break"
switch string {
case "break":
print("Before break")
break
print("After break")
default:
print("Nothing")
}
// Prints "Before break"

fallthrough

Swift 的 switch 语句不支持隐私贯穿,可以通过关键字 fallthrough 显式实现贯穿行为。

1
2
3
4
5
6
7
8
9
10
11
12
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."

语句标签

可以使用语句标签给循环语句或者条件语句做标记。在一个条件语句中,可以使用语句标签配合 break 语句来结束被标记的语句。在循环语句中,你可以使用语句标签来配合 break 或者 continue 语句来结束或者继续执行被标记的语句

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
var number = 10
whileLoop : while number > 0 {
switch number {
case 9:
print("9")
case 10:
var sum = 0
for index in 0...10 {
sum += index
if index == 9 {
print(sum)
break whileLoop
}
}
default:
break
}
number = -1
}
// Prints 45

guard

guard 语句,类似于 if 语句,基于布尔值表达式来执行语句。使用 guard 语句来要求一个条件必须是真才能执行 guard 之后的语句。与 if 语句不同,guard 语句总是有一个 else 分句——else 分句里的代码会在条件不为真的时候执行:

05

在编写条件语句的时候,左边的代码间距应该是一个“黄金”或者“快乐”的大道。不要嵌套 if 语句,多个 return 语句可能是更好的,这样可以避免圈复杂度(Cyclomatic Complexity),并且让代码更加容易阅读。因为重要部分没有嵌套在分支上,可以很清楚的找到相关代码。

推荐写法:

1
2
3
4
5
6
7
- (void)someMethod {
if (![someOther boolValue]) {
return;
}
//Do something important
}

不推荐写法:

1
2
3
4
5
- (void)someMethod {
if ([someOther boolValue]) {
//Do something important
}
}

或者:

1
2
3
4
5
6
- (void)someMethod {
if (![someOther boolValue])
return;
//Do something important
}

或者:

1
2
3
4
5
- (void)someMethod {
if (![someOther boolValue]) return;
//Do something important
}

相关文档:
https://github.com/objc-zen/objc-zen-book
https://docs.swift.org/swift-book/LanguageGuide/ControlFlow.html

应用1——验证 ip 地址的格式是否正确

例1、使用 if 语句实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
func isIPAddress(ipAddr: String) -> (Int, String) {
let compoments = ipAddr.split(separator: ".")
if compoments.count == 4 {
if let first = Int(compoments[0]), first >= 0 && first < 256 {
if let second = Int(compoments[1]), second >= 0 && second < 256 {
if let third = Int(compoments[2]), third >= 0 && third < 256 {
if let fourth = Int(compoments[3]), fourth >= 0 && fourth < 256 {
return (0, "")
} else {
return (4, "第四个数不对")
}
} else {
return (3, "第三个数不对")
}
} else {
return (2, "第二个数不对")
}
} else {
return (1, "第一个数不对")
}
}
return (100, "ip只能有四部分")
}
print(isIPAddress(ipAddr: "192.122.111.-1"))
// Prints "(4, "第四个数不对")"

例2、使用 guard 语句(注意左侧的黄金大道)实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
func isIPAddress(ipAddr: String) -> (Int, String) {
let compoments = ipAddr.split(separator: ".")
guard compoments.count == 4 else {
return (100, "ip只能有四部分")
}
guard let first = Int(compoments[0]), first >= 0 && first < 256 else {
return (1, "第一个数不对")
}
guard let second = Int(compoments[1]), second >= 0 && second < 256 else {
return (2, "第二个数不对")
}
guard let third = Int(compoments[2]), third >= 0 && third < 256 else {
return (3, "第三个数不对")
}
guard let fourth = Int(compoments[3]), fourth >= 0 && fourth < 256 else {
return (4, "第四个数不对")
}
return (0, "")
}
print(isIPAddress(ipAddr: "192.118.222.300"))
// Prints "(4, "第四个数不对")"

应用2——检查 API 可用性

Swift 拥有内置的 API 可用性的检查功能,能够确保使用不可用的 API。

可以在 if 或者 guard 语句中使用一个可用性条件来有条件地执行代码。

1
2
3
4
5
if #available(iOS 10, macOS 10.12, *) {
// 在iOS上使用iOS 10 API,在macOS上使用macOS 10.12 API
} else {
// 回到早期的iOS和macOS API
}

模式和模式匹配

模式:代表单个值或者复合值的结构。例如,x 的结构代表单个值的模式;元组 (x, y) 的结构是由逗号分隔的,包含两个元素的列表(元组模式)。

模式匹配:模式代表一种值的结构,而不是特定的某个值,可以利用模式来匹配各种各样的值。例如,(x, y) 可以匹配元组 (1, 2),以及任何两个元素的元组。

除了利用模式匹配一个值以外,也可以从复合值中提取出部分或全部值,然后分别把各个部分的值和一个常量或者变量对应起来。

模式分类

Swift 中的模式分为两类:一种能成功匹配任何类型的值,另一种在运行时匹配某个特定值时可能会失败。

  • 第一类模式:用于解构简单变量、常量和可选绑定中的值。此类模式包括通配符模式标识符模式,以及包含前两种模式的值绑定模式元组模式。可以为这类模式指定一个类型标注,从而限制它们只能匹配某种特定类型的值。

  • 第二类模式:用于全模式匹配,这种情况下匹配的值在运行时可能不存在。此类模式包括枚举用例模式可选模式表达式模式类型转换模式。在 switch 语句的 case 标签中,do 语句的 catch 子句中,或者在 ifwhileguardfor-in 语句的 case 条件句中使用这类模式。

通配符模式(Wildcard Pattern)

通配符模式:由一个下划线 _ 构成,用于匹配并忽略任何值。如在便利时,忽略被匹配的值时可以使用该模式。

1
2
3
for _ in 1...3 {
// TODO
}

标识符模式(Identifier Pattern)

标识符模式:匹配任何值,并将匹配的值和一个变量或常量绑定起来。如定义一个常量:

1
let someValue = 42

值绑定模式(Value-Binding Pattern)

值绑定模式:把匹配到的值绑定给一个变量或常量。如把匹配到的值绑定给常量时,用关键字 let,绑定给变量时,用关键字 var

1
2
3
4
5
6
let point = (3, 2)
switch point {
// 将 point 中的元素绑定到 x 和 y
case let (x, y):
print("The point is at (\(x), \(y)).")
}

元组模式(Tuple Pattern)

元组模式:是有逗号分隔的,具有零个或多个模式的列表,并由一对圆括号括起来(如:(x, y))。元组模式匹配相应元组类型的值。

可以使用类型标注限制一个元组模式能匹配哪种元组类型。例如,在常量声明 let(x, y): (Int, Int) = (1, 2) 中的元组模式 (x, y): (Int, Int) 只能匹配两个元素都是 Int 类型的元组。

当元组模式被用于 for-in 语句或者常量和变量声明时,它仅可以包含通配符模式标识符模式可选模式或者其他包含这些模式的元组模式

例如,在 for-in 语句中,使用通配符模式匹配 y == 0 的元组。

1
2
3
4
let points = [(0, 0), (1, 0), (1, 1), (2, 0), (2, 1)]
for (x, y) in points where y == 0 {
print("\(x) and \(y)")
}

打印结果:

1
2
3
0 and 0
1 and 0
2 and 0

没有正确使用元组模式则会报错,如 (x, 0) 不是正确的元组模式:

06

模式都是字母,而不是一个确切的数字,0 是数字,可以用 y 代替 0。

枚举用例模式(Enumeration Case Pattern)

枚举用例模式:匹配现有的某个枚举类型的某个用例。枚举用例模式出现在 switch 语句中的 case 标签中,以及 ifwhileguardfor-in 语句的 case 条件中。

可选项目模式(Optional Pattern)

可选项模式匹配 Optional<Wrapped> 枚举在 some(Wrapped) 中包装的值。

1
2
3
4
5
let someOptional: Int? = 42
if case .some(let x) = someOptional {
print(x)
}
// Prints "42"

或者

1
2
3
4
if case let x? = someOptional {
print(x)
}
// Prints "42"

可选项模式为 for-in 语句提供了一种迭代数组的简便方式,只为数组中非 nil 的元素执行循环体。

1
2
3
4
let arrayOfOptionalInts: [Int?] = [nil, 2, 3, nil, 5]
for case let number? in arrayOfOptionalInts {
print("Found a \(number)")
}

打印结果:

1
2
3
Found a 2
Found a 3
Found a 5

类型转换模式(Type-Casting Pattern)

有两种类型转换模式,is 模式和 as 模式。is 模式只出现在 switch 语句中的 case 标签中。

  • is 模式:当一个值的类型在运行时和 is 模式右边的指定类型一致,或者是其子类的情况下,才会匹配这个值。is 模式和 is 运算符有相似表现,它们都进行类型转换,但是 is 模式没有返回类型。

  • as 模式:当一个值的类型在运行时和 as 模式右边的指定类型一致,或者是其子类的情况下,才会匹配这个值。如果匹配成功,被匹配的值的类型被转换成 as 模式右边指定的类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
protocol Animal {
var name: String { get }
}
struct Dog: Animal {
var name: String {
return "dog"
}
var runSpeed: Int
}
struct Bird: Animal {
var name: String {
return "bird"
}
var flightHeight: Int
}
struct Fish: Animal {
var name: String {
return "fish"
}
var depth: Int
}
let animals:[Any] = [Dog(runSpeed: 55), Bird(flightHeight: 2000), Fish(depth: 100)]
for animal in animals {
switch animal {
case let dog as Dog:
print("\(dog.name) can run \(dog.runSpeed)")
case let fish as Fish:
print("\(fish.name) can dive depth \(fish.depth)")
case is Bird:
print("bird can fly")
default:
print("unknow animal!")
}
}
// dog can run 55
// bird can fly
// fish can dive depth 100

表达式模式(Expression Pattern)

表达式模式代表表达式的值。表达式模式只出现在 switch 语句中的 case 标签中。

表达式模式代表的表达式,会使用 Swift 标准库中的 ~= 运算符,与输入表达式的值进行比较。如果 ~= 运算符返回 true,则匹配成功。

默认情况下,~= 运算符使用 == 运算符来比较两个相同类型的值。它也可以将一个整型数值与一个 Range 实例中的一段整数区间做匹配。

1
2
3
4
5
6
7
8
9
10
11
let point = (1, 2)
switch point {
case (0, 0):
print("(0, 0) is at the origin.")
case (-2...2, -2...2):
print("(\(point.0), \(point.1)) is near the origin.")
default:
print("The point is at (\(point.0), \(point.1)).")
}
// Prints "(1, 2) is near the origin."

可以重载 ~= 运算符来提供自定义的表达式匹配行为:

1
2
3
4
5
6
7
8
9
10
11
12
13
func ~= (pattern: String, value: Int) -> Bool {
return pattern == "\(value)"
}
let point = (1, 2)
switch point {
case ("0", "0"):
print("(0, 0) is at the origin.")
default:
print("The point is at (\(point.0), \(point.1)).")
}
// Prints "The point is at (1, 2)."

自定义类型默认也是无法进行表达式模式匹配的,也需要重载 ~= 运算符。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
struct Employee {
var salary: Float
}
let e = Employee(salary: 9999)
func ~=(lhs: Range<Float>, rhs: Employee) -> Bool {
return lhs.contains(rhs.salary)
}
switch e {
case 0.0..<1000:
print("艰难生活")
case 1000..<5000:
print("小康生活")
case 5000..<10000:
print("活的和滋润")
default:
break
}
// Prints "活的和滋润"