Swift learning part 5 - 控制流

Swift 提供了多种流程控制结构,包括可以多次执行任务的 while 循环,基于特定条件选择执行不同代码分支的 if、guard 和 switch 语句,还有控制流程跳转到其他代码位置的 break 和 continue 语句。

Swift 还提供了 for-in 循环,用来更简单地遍历数组(Array),字典(Dictionary),区间(Range),字符串(String)和其他序列类型。

Swift 的 switch 语句比许多类 C 语言要更加强大。case 还可以匹配很多不同的模式,包括范围匹配,元组(tuple)和特定类型匹配。switch 语句的 case 中匹配的值可以声明为临时常量或变量,在 case 作用域内使用,也可以配合 where 来描述更复杂的匹配条件。

For-In 循环

可以使用 for-in 循环遍历一个集合中的所有元素,例如数组中的元素、范围内的数字或者字符串中的字符。

遍历数组:

let names = ["Anna", "Alex", "Brian", "Jack"]
for name in names {
    print("Hello, \(name)!")
}

遍历字典:

let numberOfLegs = ["spider": 8, "ant": 6, "cat": 4]
for (animalName, legCount) in numberOfLegs {
    print("\(animalName)s have \(legCount) legs.")
}

遍历数字范围:

for index in 1...5 {
    print("\(index) times 5 is \(index * 5)")
}

上面的例子中,index 是一个每次循环遍历开始时被自动赋值的常量。这种情况下,index 在使用前不需要声明,只需要将它包含在循环的声明中,就可以对其进行隐式声明,而无需使用 let 关键字声明。

如果不需要区间序列内每一项的值,可以使用下划线 _ 替代变量名来忽略这个值。例如下面例子,计算 base 这个数的 power 次幂:

let base = 3
let power = 10
var answer = 1
for _ in 1...power {
    answer *= base // 不需要区间序列的值
}

某些情况下,不想使用闭区间,包括两个端点,例如在手表上绘制分钟的刻度线,总共60个刻度,从0分开始:

let minutes = 60
for tickMark in 0..<minutes {
    print(tickMark)
}

也可以每5分钟作为一个刻度,用 stride(from:to:by:) 函数跳过不需要的标记(适用于左闭右开区间):

let minuteInterval = 5
for tickMark in stride(from: 0, to: minutes, by: minuteInterval) {
    print(tickMark)
}

可以在闭区间使用 stride(from:through:by:) 起到同样的作用:

let hours = 12
let hourInterval = 3
for tickMark in stride(from: 3, through: hours, by: hourInterval) {
    print(tickMark)
}

While 循环

while 循环会一直运行一段语句直到条件变成 false。这类循环适合使用在第一次迭代前,迭代次数未知的情况下。

Swift 提供两种 while 循环形式:
while 循环,每次在循环开始时计算条件是否符合;
repeat-while 循环,每次在循环结束时计算条件是否符合。

While

while 循环从计算一个条件开始,如果条件为 true,会重复运行一段语句,直到条件变为 false。

var number = 0
while number < 10 {
    number += 1
    print(number)
}

Repeat-While

while 循环的另外一种形式是 repeat-while,它和 while 的区别是在判断循环条件之前,先执行一次循环的代码块,然后重复循环直到条件为 false。Swift 语言的 repeat-while h循环和其他语言的 do-while 循环是类似的。

repeat {
    number += 1
    print(number)
} while  number < 20

条件语句

根据特定的条件执行特定的代码通常是十分有用的。当错误发生时,你可能想运行额外的代码;或者,当值太大或太小时,向用户显示一条消息。要实现这些功能,你就需要使用条件语句。

Swift 提供两种类型的条件语句:if 语句和 switch 语句。通常,当条件较为简单且可能的情况很少时,使用 if 语句。而 switch 语句更适用于条件较复杂、有更多排列组合的时候。并且 switch 在需要用到模式匹配(pattern-matching)的情况下会更有用。

If

只包含1个条件:

if number > 10 {
    print("number \(number) is larger than 10")
}

包含2个条件:

if number <= 10 {
    print("number \(number) is not larger than 10")
} else {
    print("number \(number) is larger than 10")
}

包含多个条件:

if number < 10 {
    print("number \(number) is smaller than 10")
} else if number == 10 {
    print("number \(number) is equal to 10")
} else {
    print("number \(number) is larger than 10")
}

Switch

switch 语句由多个 case 构成,每个由 case 关键字开始,每一个 case 都是代码执行的一条分支.switch 语句会决定哪一条分支应该被执行,这个流程被称作根据给定的值切换。

switch语句必须是完备的。这就是说,每一个可能的值都必须至少有一个 case 分支与之对应。在某些不可能涵盖所有值的情况下,你可以使用默认(default)分支来涵盖其它所有没有对应的值,这个默认分支必须在switch语句的最后面。

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")
}

不存在隐式的贯穿

与 C 和 Objective-C 中的switch语句不同,在 Swift 中,当匹配的 case 分支中的代码执行完毕后,程序会终止switch语句,而不会继续执行下一个 case 分支。这也就是说,不需要在 case 分支中显式地使用break语句。这使得switch语句更安全、更易用,也避免了因忘记写break语句而产生的错误。

注意: 虽然在Swift中break不是必须的,但你依然可以在 case 分支中的代码执行完毕前使用break跳出。

每一个 case 分支都必须包含至少一条语句。像下面这样书写代码是无效的,因为第一个 case 分支是空的:

let anotherCharacter: Character = "a"
switch anotherCharacter {
case "a": // 无效,这个分支下面没有语句
case "A":
    print("The letter A")
default:
    print("Not the letter A")
}

这段代码会报编译错误。

不像 C 语言里的switch语句,在 Swift 中,switch语句不会一起匹配"a"和"A"。相反的,上面的代码会引起编译期错误:case “a”: 不包含任何可执行语句。这就避免了意外地从一个 case 分支贯穿到另外一个,使得代码更安全、也更直观。

为了让单个case同时匹配a和A,可以将这个两个值组合成一个复合匹配,并且用逗号分开。

let anotherCharacter: Character = "a"
switch anotherCharacter {
case "a", "A":
    print("The letter A")
default:
    print("Not the letter A")
}

注意:如果想要显式贯穿 case 分支,请使用 fallthrough 语句。

区间匹配

case 分支的模式也可以是一个值的区间。

let money = 200
let description :String
switch money {
case 0:
    description = "no"
    
case 1..<10:
    description = "a few"
    
case 10..<100:
    description = "some"
    
case 100..<1000:
    description = "hundreds of"
    
default:
    description = "a lot of"
}
print("I have \(description) money")

元组

我们可以使用元组在同一个 switch 语句中测试多个值,元组中的元素可以是值,也可以是区间,使用下划线 _ 来匹配所有可能的值。

下面的例子展示了如何使用一个 (Int, Int) 类型的元组来分类下图中的点:

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")
}

不像 C 语言,Swift 允许多个 case 匹配同一个值。实际上,在这个例子中,点(0, 0)可以匹配所有四个 case。但是,如果存在多个匹配,那么只会执行第一个被匹配到的 case 分支。考虑点(0, 0)会首先匹配case (0, 0),因此剩下的能够匹配的分支都会被忽视掉。

值绑定(Value Bindings)

case 分支允许将匹配的值声明为临时常量或变量,并且在case分支体内使用,这种行为被称为值绑定(value binding),因为匹配的值在case分支体内,与临时的常量或变量绑定。

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))")
}

Where

case 分支的模式可以使用where语句来判断额外的条件。下面的例子把下图中的点(x, y)进行了分类:

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")
}

复合匹配

当多个条件可以使用同一种方法来处理时,可以将这几种可能放在同一个 case 后面,并且用逗号隔开。

let character: Character = "e"
switch character {
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")
}

复合匹配也可以包含值绑定。

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")
}

控制转移语句

控制转移语句改变你代码的执行顺序,通过它可以实现代码的跳转,Swift 有5种控制转移语句:
continue
break
fallthrough
return
throw

Continue

continue 语句告诉一个循环体立刻停止本次循环,重新开始下次循环,但是并不会离开整个循环体。下面的例子把一个小写字符串中的元音字母和空格字符移除,生成一个含义模糊的短句:

let puzzleInput = "great minds think alike"
var puzzleOutput = ""
for character in puzzleInput {
    switch character {
    case "a", "e", "i", "o", "u", " ":
        continue
        
    default:
        puzzleOutput.append(character)
    }
}
print(puzzleOutput)

Break

break 语句会立刻结束整个控制流的执行,break 可以在 switch 或循环语句中使用,用来提前结束 switch 或循环语句。

循环语句中的 break

当在一个循环体中使用 break 时,会立刻中断该循环的执行,然后跳转到表示循环体结束的大括号 ( } ) 后的第一行代码。不会再有本次循环的代码被执行,也不会再有下次的循环产生。

Switch 语句中的 break

当在一个 switch 代码块中使用 break 时,会立即中断该 switch 代码块的执行,并且跳转到表示 switch 代码块结束的大括号( } )后的第一行代码。

这种特性可以被用来匹配或者忽略一个或多个分支。因为 Swift 的 switch 需要包含所有的分支而且不允许有为空的分支,有时为了使你的意图更明显,需要特意匹配或者忽略某个分支。那么当你想忽略某个分支时,可以在该分支内写上 break 语句。当那个分支被匹配到时,分支内的 break 语句立即结束 switch 代码块。

在 switch 语句中使用 break,可以匹配或者忽略一个或多个分支,例如下边的例子:
switch character {
case “a” :
print(print(“character a”))

default:
break
}

贯穿(Fallthrough)

在 Swift 里,switch语句不会从上一个 case 分支跳转到下一个 case 分支中。相反,只要第一个匹配到的 case 分支完成了它需要执行的语句,整个switch代码块完成了它的执行。

相比之下,C 语言要求你显式地插入break语句到每个 case 分支的末尾来阻止自动落入到下一个 case 分支中。

Swift 的这种避免默认落入到下一个分支中的特性意味着它的switch 功能要比 C 语言的更加清晰和可预测,可以避免无意识地执行多个 case 分支从而引发的错误。如果你确实需要 C 风格的贯穿的特性,你可以在每个需要该特性的 case 分支中使用fallthrough关键字。

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

带标签的语句

在 Swift 中,你可以在循环体和条件语句中嵌套循环体和条件语句来创造复杂的控制流结构。并且,循环体和条件语句都可以使用break语句来提前结束整个代码块。因此,显式地指明break语句想要终止的是哪个循环体或者条件语句,会很有用。类似地,如果你有许多嵌套的循环体,显式指明continue语句想要影响哪一个循环体也会非常有用。

为了实现这个目的,你可以使用标签(statement label)来标记一个循环体或者条件语句,对于一个条件语句,你可以使用break加标签的方式,来结束这个被标记的语句。对于一个循环语句,你可以使用break或者continue加标签,来结束或者继续这条被标记语句的执行。

声明一个带标签的语句是通过在该语句的关键词的同一行前面放置一个标签,作为这个语句的前导关键字,并且该标签后面跟随一个冒号。

下面是一个针对while循环体的标签语法,同样的规则适用于所有的循环体和条件语句。

label name: while condition {
    statements
}

模拟掷骰子游戏,规定掷出偶数加对应的分值,掷出奇数不加分,掷出6或总分达到20分以上直接结束游戏:

var totalScore = 0
var randomNumber = 0
gameLoop: while totalScore < 20 {
    randomNumber = Int(arc4random()%6 + 1);
    switch randomNumber {
    case 1, 3, 5:
        continue gameLoop
        
    case 6:
        break gameLoop
        
    default:
        totalScore += randomNumber
    }
}
print("Game over!")

提前退出

像 if 语句一样,guard 的执行取决于一个表达式的布尔值。我们可以使用 guard 语句来要求条件必须为真时,以执行 guard 语句后的代码。不同于 if 语句,一个 guard 语句总是有一个 else 从句,如果条件不为真则执行 else 从句中的代码。

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"])
greet(person: ["name": "Jane", "location": "Cupertino"])

如果 guard 语句的条件被满足,则继续执行 guard 语句大括号后的代码。将变量或者常量的可选绑定作为 guard 语句的条件,都可以保护 guard 语句后面的代码。

如果条件不被满足,在 else 分支上的代码就会被执行。这个分支必须转移控制以退出 guard 语句出现的代码段。可以用控制转移语句如 return、break、continue 或 throw 做这件事,或调用一个不返回的方法或函数如fatalError()。

相比于可以实现同样功能的if语句,按需使用 guard 语句会提升我们代码的可读性。它可以使你的代码连贯的被执行而不需要将它包在 else 块中,它可以使你在紧邻条件判断的地方,处理违规的情况。

检测 API 可用性

Swift内置支持检查 API 可用性,这可以确保我们不会在当前部署机器上,不小心地使用了不可用的API。

编译器使用 SDK 中的可用信息来验证我们的代码中使用的所有 API 在项目指定的部署目标上是否可用。如果我们尝试使用一个不可用的 API,Swift 会在编译时报错。

我们在 if 或 guard 语句中使用可用性条件(availability condition)去有条件的执行一段代码,来在运行时判断调用的API是否可用。编译器使用从可用性条件语句中获取的信息去验证,在这个代码块中调用的 API 是否可用。

if #available(iOS 10, macOS 10.12, *) {
    // 在 iOS 使用 iOS 10 的 API, 在 macOS 使用 macOS 10.12 的 API
} else {
    // 使用先前版本的 iOS 和 macOS 的 API
}

以上可用性条件指定,if 语句的代码块仅仅在 iOS 10 或 macOS 10.12 及更高版本才运行。最后一个参数 * 是必须的,用于指定在所有其它平台中,如果版本号高于你的设备指定的最低版本,if 语句的代码块将会运行。

在它一般的形式中,可用性条件使用了一个平台名字和版本的列表。平台名字可以是iOS、macOS、watchOS 和 tvOS。除了指定像 iOS 8 或 macOS 10.10 的大版本号,也可以指定像 iOS 8.3 以及 macOS 10.10.3 的小版本号。

if #available(平台名称 版本号, ..., *) {
    APIs 可用,语句将执行
} else {
    APIs 不可用,语句将不执行
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值