Swift-控制流

Swift提供了多种控制流声明。包括while循环来多次执行一个任务;ifguardswitch声明来根据确定条件执行不同的分支代码;以及像breakcontinue的声明来进行控制转移。
Swift同样提供了一个for-in循环来方便的遍历数组,字典,区间,字符串和其它序列。
Swift的Switch声明相比一些类C语言的同伴要强大的多。case可以匹配许多不同的模式,包括间隔匹配,元组和转换到一个指定类型。Switchcase中匹配的值可以转换为临时的常量或者变量来在case体内使用,并且可以使用where字句在每个case中表达复杂的匹配条件。

目录

  • For-In循环
  • While循环
  • 条件声明
  • 控制转移声明
  • 提前退出
  • 检查API可用性

For-In循环

使用for-in循环遍历一个序列,例如数组的元素,值的范围或者字符串的字符。
这个例子使用for-in循环遍历一个数组的元素:

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

同样可以遍历字典来访问它的键值对。遍历字典时它的每个元素都被当做(key, value)类型的元组返回,可以将这个元组的成员分解成有明确名字的常量以便在for-in循环体内使用。下例中,字典的键被分解为名为animalName的常量,值被分解为名为legCount的常量。

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

字典的元素是无序的,遍历这些元素时不能保证它们被访问的顺序。特别的,向字典插入元素的顺序并没有定义它们被遍历的顺序。更多数组和字典的内容,详见Swift-集合类型
for-in循环同样适用于数值范围。这个例子打印了一个五倍表的前几个条目:

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

这个被遍历的序列是一个从15(包括5,因为使用了闭区间运算符...)的数值区间。index的值被设置成区间的第一个值,然后执行循环体内的声明。本例中,这个循环只包含一个声明,用来打印index当前值在五倍表的条目。当执行完声明后,index的值更新为区间的第二个值,函数print(_:separator:terminator:)再次被调用。这个过程持续到区间的末尾。
上例中,index是一个常量,它的值在循环每次遍历开始时被自动设置。因此index在使用之前无需声明。它通过被包含在循环声明中而被隐式的声明,无需let声明关键字。
如果不需要一个序列的每个值,可以通过使用下划线代替变量名来忽略这个值。

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"

上例计算了某个数的n次幂(例子中是3的10次幂)。它使用一个从110的闭区间将基数1连乘10次3。对于这个计算,循环每次的计数值是没有必要的,代码本身就执行了正确的次数。使用下划线代替循环变量会忽略计数值并且不能访问每次循环的当前值。
在某些情况下,你不想使用包含边界的闭区间。假设想在表盘上每隔一分钟绘制一条刻度线。你想从0分钟开始绘制60条刻度线。使用半开区间运算符(..<)来包括更低的边界但是不包括更高的边界。

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

一些用户在界面上想要更少的刻度线。他们更喜欢每隔5秒绘制一条刻度线。使用stride(from:to:by:)函数跳过不想要的刻度。

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

闭区间同样适用,但要使用stride(from:through:by:)函数:

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

While循环

While循环反复执行一段语句直到一个条件为false。这种循环最佳的使用场景是在第一次循环开始前遍历次数未知的情况。Swift提供了两种While循环:

  • While每次循环开始前会评估条件
  • repeat-while每次循环结束后会评估条件

While

While循环先评估一个单独的条件再开始。如果条件为true,反复执行一组语句直到条件为false
下面是一个While循环的通常格式:

while condition {
    statements
}

这个例子玩了一个简单的蛇和梯子 的游戏:

蛇和梯子

这个游戏的规则如下:

  • 这个板子有25个方块,目标是到达或者超过第25个方块
  • 玩家从左下角的“方块0”开始
  • 每个回合,你摇一个六边的的骰子来或者去前进的步数,前进路线如右图的点状箭头所示
  • 如果你走到了梯子的底部,爬到梯子的顶部
  • 如果到了蛇的头部,爬到蛇的尾部

这个游戏板子表示为一个Int值的数组。这个数组的大小取决于一个名为finalSquare的常量,这个常量用来初始化这个数组并检查胜利的条件。以为玩家在板子外部开始,也就是“方块0”,因此板子初始化为包含26个0而不是25.

let finalSquare = 25
var board = [Int](repeating: 0, count: finalSquare + 1)

然后一些方块为蛇和梯子设置了指定的值。含梯底的方块有一个正值帮助你向前移动,然而含蛇头的梯子有一个负值让你向后后退。

board[03] = +08; board[06] = +11; board[09] = +09; board[10] = +02
board[14] = -10; board[19] = -11; board[22] = -02; board[24] = -08

方块3包含一个梯子的底部,帮助你移动到方块11。为了表示这个,board[03]赋值为+08,等价于整型值8(311的差值)。为了对齐值和声明,一元加运算符(+)显示的与一元减运算符(-)配合使用,并且低于10的值加0补齐。(虽然格式上的技巧不是严格必要的,但是它们可以保证整洁的代码)

var square = 0
var diceRoll = 0
while square < finalSquare {
    // roll the dice
    diceRoll += 1
    if diceRoll == 7 { diceRoll = 1 }
    // move by the rolled amount
    square += diceRoll
    if square < board.count {
        // if we're still on the board, move up or down for a snake or a ladder
        square += board[square]
    }
}
print("Game over!")

上面的例子使用了一个非常简单的方式模拟掷骰子。diceRoll的值不是一个随机值,它的起始值是0。每次while循环,diceRoll的值加1并检查值是否过大。一旦diceRoll的值等于7,骰子值就变得过大因此重置为1。结果diceRoll的值总是12345612循环。
掷骰子后,玩家前进diceRoll步。有可能玩家超出了方块25,这时游戏结束。为了处理这种情况,代码会检查square小于board数组的count属性。如果square是有效的,存储在board[square]的值会加到square让玩家爬上梯子或者滑到蛇尾。

注意
如果不进行检查,board[square]也许会试图获取超出board数组边界的值,这会导致运行时错误。

当前的while循环然后结束,然后检查循环条件来确定是否继续执行循环。如果玩家已经到达或者越过方块25,那么循环条件为false游戏结束。
这个例子使用while循环是合适的,因为当while循环开始时游戏的长度是不确定的。相反,直到满足一个特殊条件循环才会终止。

Repeat-While

另一个while循环的变种是repeat-while循环,它在考虑循环条件之前先执行一遍循环代码。然后继续循环直到条件为假。

注意
Swift的repeat-while循环和其它语言的do-while是类似的。

以下是repeat-while循环的通用格式:

repeat {
    statements
} while condition

下面的例子使用repeat-while循环重写的蛇和梯子的游戏。finalSquareboardsquarediceRoll值的初始化是和while循环一样。

let finalSquare = 25
var board = [Int](repeating: 0, count: finalSquare + 1)
board[03] = +08; board[06] = +11; board[09] = +09; board[10] = +02
board[14] = -10; board[19] = -11; board[22] = -02; board[24] = -08
var square = 0
var diceRoll = 0

这个版本的游戏中,循环中第一步检查梯子或者蛇。板子中没有梯子可以让玩家直接到达方块25,因此不可能通过爬上一个梯子还赢得游戏。因此循环的第一步检查蛇或者梯子是安全的。
在游戏的开始,玩家站在“方块0”。board[0]总是等于0

repeat {
    // move up or down for a snake or ladder
    square += board[square]
    // roll the dice
    diceRoll += 1
    if diceRoll == 7 { diceRoll = 1 }
    // move by the rolled amount
    square += diceRoll
} while square < finalSquare
print("Game over!")

当代码检查了蛇和梯子后,开始掷骰子,玩家前进diceRoll步。当前循环结束。循环条件(while square < finalSquare)和之前一样,但是这次直到第一次循环完毕才评估。在这个例子中repeat-while循环结构要比while循环结构更合适。在repeat-while循环中,当循环的while条件确认square仍在板子上后,square += board[square]总是立刻执行。这种方式避免了之前版本检查数组边界的需求。

条件声明

通常需要根据指定的条件执行不同的代码片段。当一个错误发生时,你也许需要一段额外的代码,或者当一个值过大或者过小时展示一条信息。想做到这样,可以使你的部分代码是有条件的。
Swift提供了两种方式添加条件分支:if声明和switch声明。通常,使用if声明来评估只包含几个可能输出的简单情况。switch声明更适用于包含多个可能排列的更加复杂的情况,并且在需要模式匹配来帮助选择合适的代码分支执行的情况下更加有用。

If

if声明最简单的形式只有一个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."

上面的例子检查温度是否小等于32华氏度。如果是真,打印一条消息,否则不打印消息继续执行if声明终止括号后面的语句。
if声明可以提供可供选择的语句集合,叫做else从句。用在if条件为false时。这些语句由else关键字指示。

temperatureInFahrenheit = 40
if temperatureInFahrenheit <= 32 {
    print("It's very cold. Consider wearing a scarf.")
} else {
    print("It's not that cold. Wear a t-shirt.")
}
// Prints "It's not that cold. Wear a t-shirt."

两个分支总有一个被执行。因为温度上升到了40华氏度,不算太冷,不再需要建议围围巾,因此else分支被触发。
可以将多个if语句链接在一起来实现额外的分支。

temperatureInFahrenheit = 90
if temperatureInFahrenheit <= 32 {
    print("It's very cold. Consider wearing a scarf.")
} else if temperatureInFahrenheit >= 86 {
    print("It's really warm. Don't forget to wear sunscreen.")
} else {
    print("It's not that cold. Wear a t-shirt.")
}
// Prints "It's really warm. Don't forget to wear sunscreen."

这里,一个额外的if语句用来处理特别热的温度。最后的else语句保留了下来,用于打印既不冷也不热时的消息。
实际上最后的else语句是可选的,当不需要完整判断情况的时候可以排除。

temperatureInFahrenheit = 72
if temperatureInFahrenheit <= 32 {
    print("It's very cold. Consider wearing a scarf.")
} else if temperatureInFahrenheit >= 86 {
    print("It's really warm. Don't forget to wear sunscreen.")
}

因为温度既不冷也不热,所以不会触发ifelse if分支,也就不会打印任何消息。

Switch

switch语句会尝试把某个值与若干个模式进行匹配。根据第一个匹配成功的模式,switch语句会执行对应的代码。当处理多个可能得状态时,通常用switch语句替换if语句。
switch语句最简单的形式就是把某个值与一个或多个相同类型的值作比较:

switch some value to consider {
case value 1:
    respond to value 1
case value 2,
     value 3:
    respond to value 2 or 3
default:
    otherwise, do something else
}

每个switch语句由多个可能的case构成,每个由case关键字开始。为了匹配某些更特定的值,Swift 提供了几种方法来进行更复杂的模式匹配,这些模式将在本节的稍后部分提到。
if语句的语句体类似,每一个case都是代码执行的一条分支。switch语句会决定哪一条分支应该被执行,这个流程被称作根据给定的值切换。
每一个switch语句必须是穷举的。这就是说,每一个可能的值都必须有一个case分支与之对应。在某些不可能涵盖所有值的情况下,你可以使用默认分支来涵盖其它所有没有对应的值,这个默认分支由default关键字表示,且必须在switch语句的最后面。
下面的例子使用switch语句来匹配一个名为someCharacter的小写字符:

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"

switch语句的第一个case分支用于匹配第一个英文字母a,第二个case分支用于匹配最后一个字母z。 因为switch语句必须有一个case分支用于覆盖所有可能的字符,而不仅仅是所有的英文字母,所以switch语句使用一个default分支来匹配除了az外的所有值,这个分支保证了swith语句的穷举的。

不存在隐式的贯穿

与C和OC中的switch语句不同,在 Swift 中,它不会默认的自动贯穿每个分支的底部进入下一个分支。相反,当第一个匹配的case执行完毕后,整个switch语句会终止,不需显式地使用break语句。这使得switch语句相比于C语言更安全、更易用,也避免了错误的执行多个case分支。

注意
虽然在Swift中break不是必须的,但你依然可以使用break语句来匹配或忽略一个特殊的分支或者在一个匹配的分支的代码执行完毕前提前跳出。

每一个case分支都必须包含至少一条语句。像下面这样书写代码是无效的,因为第一个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.

不像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")
}
// Prints "The letter A"

为了可读性,一个复合匹配可以写成多行

注意
如果想要显式贯穿case分支,请使用fallthrough关键字。

区间匹配

可以用一个区间来检查switch分支的值的包含情况。下面的例子使用数值区间匹配来输出任意数字对应的自然语言格式:

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."

在上例中,approximateCount在一个switch声明中被评估。每一个case都将其与一个值或区间进行比较。因为approximateCount在12到100之间,所以naturalCount被赋值"dozens of",执行跳出switch语句。

元组

我们可以使用元组在同一个switch语句中测试多个值。元组中的每个元素可以使用值或者区间来测试。另外,使用下划线(_),也就是通配符来匹配所有可能的值。
下面的例子展示了如何使用一个(Int, Int)类型的元组来分类下图中的点(x, y)。

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"

坐标

switch语句会判断这个点是否在原点(0, 0),是否在红色的x轴上,是否在橘黄色的y轴上,是否在一个以原点为中心的4x4的蓝色矩形里,或者在这个矩形外面。
不像C语言,Swift 允许多个case匹配同一个值。实际上,在这个例子中,点(0, 0)可以匹配所有四个case。但是,如果存在多个匹配,那么只会执行第一个被匹配到的 case 分支。考虑点(0, 0)会首先匹配case (0, 0),因此剩下的能够匹配的分支都会被忽视掉。

值绑定

一个switch分支允许将匹配的值绑定到临时的常量或变量,以便在分支体内使用。这种行为被称为值绑定,因为匹配的值在case分支体内,与临时的常量或变量绑定。
下面的例子展示了如何在一个(Int, Int)类型的元组中使用值绑定来分类下图中的点(x, y):

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"
![坐标系](http://upload-images.jianshu.io/upload_images/3302097-ce4cffba0596d2e8.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
`switch`语句会判断这个点是否在红色的x轴上,是否在橘黄色的y轴上,或者不在坐标轴上。
这三个`switch`分支都声明了常量`x`和`y`的占位符,用于临时获取元组`anotherPoint`的一个或两个值。第一个分支`case (let x, 0)`将匹配一个`y`为`0`的点,并把这个点的横坐标赋给临时的常量`x`。类似的,第二个`case (0, let y)`将匹配一个`x`为`0`的点,并把这个点的纵坐标赋给临时的常量`y`。
一旦声明了这些临时的常量,它们就可以在其对应的c分支里使用。在这个例子中,它们用于打印给定点的位置。
这个`switch`语句不包含`default`分支。最后一个分支`case let(x, y)`声明了一个可以匹配任何值的元组。因为`anotherPoint`总是一个包含两个值的元组,这个分支满足所有可能剩余的值,因此无需一个`default`分支来穷举`switch`语句。
###Where
一个`switch`分支可以使用`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")
}
// Prints "(1, -1) is on the line x == -y"

坐标系

switch语句会判断这个点是否在绿色的对角线x == y上,是否在紫色的对角线x == -y上,或者不在对角线上。
这三个switch分支都声明了常量xy的占位符,用于临时获取元组yetAnotherPoint的两个值。这两个常量被用作where语句的一部分,来创建一个动态的过滤器。仅当where语句的条件为true时,匹配到的 case 分支才会被执行。
就像是值绑定中的例子,这个分支满足所有可能剩余的值,因此无需一个default分支来穷举switch语句。

复合匹配

当多个条件可以使用同一种方法来处理时,可以将这几种可能放在同一个case后面,并且用逗号隔开。当case后面的任意一种模式匹配的时候,这条分支就会被匹配。并且,如果匹配列表过长,还可以分行书写。例如:

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"

这个switch语句中的第一个case匹配了英语中的五个小写元音字母。相似的,第二个case匹配了英语中所有的小写辅音字母。最终,default分支匹配了其它所有字符。
复合匹配同样可以包含值绑定。复合匹配里所有的匹配模式,都必须包含相同的值绑定。并且每一个绑定都必须获取到相同类型的值。这保证了,无论复合匹配中的哪个模式发生了匹配,分支体内的代码,都能获取到绑定的值,并且绑定的值都有一样的类型。

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"

上面的case有两个模式:(let distance, 0)匹配了在x轴上的值,(0, let distance)匹配了在y轴上的值。两个模式都绑定了distance,并且distance在两种模式下都是整型——这意味着分支体内的代码总是能获取到distance的值。

控制转移语句

控制转移语句通过从一片代码转移控制到另一片代码来改变你代码的执行顺序。Swift有五种控制转移语句:

  • continue
  • break
  • fallthrough
  • return
  • throw

continuebreakfallthrough语句会在以下介绍。return在函数介绍,throw在错误抛出介绍。

Continue

continue语句通知一个循环停止本次循环,重新开始下次循环。就好像在说“本次循环我已经执行完了”,但是并不会离开整个循环体。
下面的例子把一个小写字符串中的元音字母和空格字符移除,生成了一个含义模糊的短句:

let puzzleInput = "great minds think alike"
var puzzleOutput = ""
let charactersToRemove: [Character] = ["a", "e", "i", "o", "u", " "]
for character in puzzleInput.characters {
    if charactersToRemove.contains(character) {
        continue
    } else {
        puzzleOutput.append(character)
    }
}
print(puzzleOutput)
// Prints "grtmndsthnklk"

在上面的代码中,只要匹配到元音字母或者空格字符,就调用continue语句使本次循环结束,重新开始下次循环。

Break

break语句会立刻结束整个控制流的执行。当你想要更早的结束一个switch代码块或者一个循环体时,你都可以使用break语句。

循环语句中的Break

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

Switch语句中的Break

当在一个switch代码块中使用break时,会立即中断该switch代码块的执行,并且跳转到表示switch代码块结束的大括号(})后的第一行代码。
这种特性可以被用来匹配或者忽略一个或多个分支。因为Swift的 switch需要包含所有的分支而且不允许有为空的分支,有时为了使你的意图更明显,需要特意匹配或者忽略某个分支。那么当你想忽略某个分支时,可以在该分支内写上break语句。当那个分支被匹配到时,分支内的break语句立即结束switch代码块。

注意
当一个switch分支仅仅包含注释时,会被报编译时错误。注释不是代码语句而且也不能让switch分支达到被忽略的效果。你应该使用break来忽略某个分支。

下面的例子通过switch来判断一个Character值是否代表下面四种语言之一。为了简洁,多个值被包含在了同一个分支情况中。

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."

这个例子检查numberSymbol是否是拉丁,阿拉伯,中文或者泰语中的14之一。如果被匹配到,该switch分支语句给Int?类型变量possibleIntegerValue设置一个整数值。
switch代码块执行完后,使用可选绑定来判断possibleIntegerValue是否曾经被设置过值。因为是可选类型的缘故,possibleIntegerValue有一个隐式的初始值nil,所以仅仅当possibleIntegerValue曾被switch代码块的前四个分支中的某个设置过一个值时,可选的绑定才会被判定为成功。
在上面的例子中,想要把Character所有的的可能性都枚举出来是不现实的,所以使用default分支来包含所有上面没有匹配到字符的情况。由于这个default分支不需要执行任何动作,所以它只写了一条break语句。一旦落入到default分支中后,break语句就完成了该分支的所有代码操作,代码继续向下,开始执行if let语句。

Fallthrough

Swift中的switch不会从上一个case分支落入到下一个case分支中。相反,只要第一个匹配到的case分支完成了它需要执行的语句,整个switch代码块完成了它的执行。相比之下,C 语言要求你显式地插入break语句到每个case分支的末尾来阻止自动落入到下一个case分支中。Swift的这种避免默认落入到下一个分支中的特性意味着它的switch功能要比 C 语言的更加清晰和可预测,可以避免无意识地执行多个case分支从而引发的错误。
如果你确实需要 C 风格的贯穿的特性,你可以在每个需要该特性的 case 分支中使用fallthrough关键字。下面的例子使用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."

这个例子定义了一个String类型的变量description并且给它设置了一个初始值。函数使用switch逻辑来判断integerToDescribe变量的值。当integerToDescribe的值属于列表中的质数之一时,该函数在description后添加一段文字,来表明这个数字是一个质数。然后它使用fallthrough关键字来“贯穿”到default分支中。default分支在description的最后添加一段额外的文字,至此switch代码块执行完了。
如果integerToDescribe的值不属于列表中的任何质数,那么它不会匹配到第一个switch分支。而这里没有其他特别的分支情况,所以integerToDescribe匹配到default分支中。
switch代码块执行完后,使用print(_:separator:terminator:)函数打印该数字的描述。在这个例子中,数字5被准确的识别为了一个质数。

注意
fallthrough关键字不会检查它下一个将会落入执行的 case 中的匹配条件。fallthrough简单地使代码继续连接到下一个 case 中的代码,这和 C 语言标准中的switch语句特性是一样的。

带标签的语句

在 Swift 中,你可以在循环体和条件语句中嵌套循环体和条件语句来创造复杂的控制流结构。并且,循环体和条件语句都可以使用break语句来提前结束整个代码块。因此,显式地指明break语句想要终止的是哪个循环体或者条件语句,会很有用。类似地,如果你有许多嵌套的循环体,显式指明continue语句想要影响哪一个循环体也会非常有用。
为了实现这个目的,你可以使用标签来标记一个循环体或者条件语句,对于一个条件语句,你可以使用break加标签的方式,来结束这个被标记的语句。对于一个循环语句,你可以使用break或者continue加标签,来结束或者继续这条被标记语句的执行。
声明一个带标签的语句是通过在该语句的关键词的同一行前面放置一个标签,作为这个语句的前导关键字,并且该标签后面跟随一个冒号。下面是一个针对while循环体的标签语法,同样的规则适用于所有的循环体和条件语句。

label name: while condition {
    statements
}

下面的例子是前面章节中蛇和梯子的适配版本,在此版本中,我们将使用一个带有标签的while循环体中调用breakcontinue语句。这次,游戏增加了一条额外的规则:

  • 为了获胜,你必须刚好落在第 25 个方块中。

如果某次掷骰子使你的移动超出第 25 个方块,你必须重新掷骰子,直到你掷出的骰子数刚好使你能落在第 25 个方块中。
游戏的棋盘和之前一样:

带标签的语句

finalSquareboardsquarediceRoll值被和之前一样的方式初始化:

let finalSquare = 25
var board = [Int](repeating: 0, count: finalSquare + 1)
board[03] = +08; board[06] = +11; board[09] = +09; board[10] = +02
board[14] = -10; board[19] = -11; board[22] = -02; board[24] = -08
var square = 0
var diceRoll = 0

这个版本的游戏使用while循环和switch语句来实现游戏的逻辑。while循环有一个标签名gameLoop,来表明它是游戏的主循环。
while循环体的条件判断语句是while square !=finalSquare,这表明你必须刚好落在方格25中。

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

每次循环迭代开始时掷骰子。与之前玩家掷完骰子就立即移动不同,这里使用了switch语句来考虑每次移动可能产生的结果,从而决定玩家本次是否能够移动。

  • 如果骰子数刚好使玩家移动到最终的方格里,游戏结束。break gameLoop语句跳转控制去执行while循环体后的第一行代码,意味着游戏结束。
  • 如果骰子数将会使玩家的移动超出最后的方格,那么这种移动是不合法的,玩家需要重新掷骰子。continue gameLoop语句结束本次while循环,开始下一次循环。
  • 在剩余的所有情况中,骰子数产生的都是合法的移动。玩家向前移动 diceRoll 个方格,然后游戏逻辑再处理玩家当前是否处于蛇头或者梯子的底部。接着本次循环结束,控制跳转到while循环体的条件判断语句处,再决定是否需要继续执行下次循环。

注意
如果上述的break语句没有使用gameLoop标签,那么它将会中断switch语句而不是while循环。使用gameLoop标签清晰的表明了break想要中断的是哪个代码块。 同时请注意,当调用continue gameLoop去跳转到下一次循环迭代时,这里使用gameLoop标签并不是严格必须的。因为在这个游戏中,只有一个循环体,所以continue语句会影响到哪个循环体是没有歧义的。然而,continue语句使用gameLoop标签也是没有危害的。这样做符合标签的使用规则,同时参照旁边的break gameLoop,能够使游戏的逻辑更加清晰和易于理解。

提前退出

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"])
// 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."

如果guard语句的条件被满足,则继续执行guard语句大括号后的代码。将变量或者常量的可选绑定作为guard语句的条件,都可以保护guard语句后面的代码。
如果条件不被满足,在else分支上的代码就会被执行。这个分支必须转移控制以退出guard语句出现的代码段。它可以用控制转移语句如return,break,continue或者throw做这件事,或者调用一个不返回的方法或函数,例如fatalError(_:file:line:)
相比于可以实现同样功能的if语句,按需使用guard语句会提升我们代码的可读性。它可以使你的代码连贯的被执行而不需要将它包在else块中,它可以使你在紧邻条件判断的地方,处理违规的情况。

检测 API 可用性

Swift内置支持检查 API 可用性,这可以确保我们不会在当前部署机器上,不小心地使用了不可用的API。
编译器使用 SDK 中的可用信息来验证我们的代码中使用的所有 API 在项目指定的部署目标上是否可用。如果我们尝试使用一个不可用的 API,Swift 会在编译时报错。
我们在ifguard语句中使用可用性条件去有条件的执行一段代码,来在运行时判断调用的API是否可用。编译器使用从可用性条件语句中获取的信息去验证,在这个代码块中调用的 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
}

以上可用性条件指定,在iOS中,if语句的代码块仅仅在 iOS 10 及更高的系统下运行;在 macOS中,仅在 macOS 10.12 及更高才会运行。最后一个参数,*,是必须的,用于指定在所有其它平台中,如果版本号高于你的设备指定的最低版本,if语句的代码块将会运行。
在它一般的形式中,可用性条件使用了一个平台名字和版本的列表。平台名字可以是iOSmacOSwatchOStvOS——请访问声明属性来获取完整列表。除了指定像 iOS 8或者macOS 10.10的主板本号,我们可以指定像iOS 8.3 以及 macOS 10.10.3的子版本号。

if #available(platform name version, ..., *) {
    statements to execute if the APIs are available
} else {
    fallback statements to execute if the APIs are unavailable
}

上一篇:Swift-集合类型

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 151,511评论 1 330
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 64,495评论 1 273
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 101,595评论 0 225
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 42,558评论 0 190
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 50,715评论 3 270
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 39,672评论 1 192
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,112评论 2 291
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 29,837评论 0 181
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 33,417评论 0 228
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 29,928评论 2 232
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,316评论 1 242
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 27,773评论 2 234
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,253评论 3 220
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 25,827评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,440评论 0 180
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 34,523评论 2 249
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 34,583评论 2 249

推荐阅读更多精彩内容