控制流(Control Flow)

967 阅读29分钟

swift提供了丰富的控制流语句。包括while循环来多次执行一个任务;if,guard和switch语句来根据确定的条件执行不同的代码分支;也有像break和continue语句来切换执行到你代码中另外一个地方。

swift也提供了for-in循环使遍历数组、字典、区间、字符串和其他序列变得简单。

swift的switch语句比它在许多像C的语言中更强大。cases可以匹配许多不同的模式,包括区间匹配,元祖,和解包成一个指定的类型。匹配switch的case的值可以绑定到临时常量后者变量来在case的主体中使用,并且复杂的匹配条件每个case可以使用where从句表示。

For-In Loops

使用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!

你也可以遍历一个Dictionary来访问它的key-value对。当字典被遍历时字典中的每一个对象按(key,value)的元祖返回,你可以将(key,value)元祖的元素解压为具体名字的常量在for-in的主体中使用。在下面的代码例子中,字典的key解压到了名为animalName的常量中,字典的值解压到名为legCount的常量中。

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

字典的内容本来就是无序的,遍历它不能保证他们获取的顺序。尤其是,你插入Dictionary中对象的顺序不能决定他们遍历的顺序,更多关于数组和字典的信息,查看Collection Types

你也可以用数字区间使用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

序列在1到5的范围中被遍历,包括,像使用闭合区间操作符表示的。index的值在区间(1)中设置成第一个数字,然后执行循环体中的语句。这种情况,循环体只含有一个语句,打印当前index的值的5的倍数列表的一个值。语句执行之后,index的值更新为第二个在区域(2)的值,然后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"

上面的例子计算了一个值对另一个值的次方(这里,3的10次方)。这里用3乘以一个初始值1,诗词,使用一个1开始到10的闭区间。这个计算,每次循环时独立计数器的值不需要了--代码只是简单地执行正确数目次的循环。下划线替代变量使单独的值忽略了并且每次循环时不提供访问当前的值。

一些情况下,你可能想使用闭合区间,包括两个端点。考虑在表面上画每分钟的记号。你想画60个标记,从0分钟开始。使用半开区间(..<)包括起点不包含终点。更多关于区间的信息,查看Range Operators

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

有的用户可能想要少一点的指针标记在UI中。他们可以每五分钟加一个标记。使用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 Loops)

一个while循环从执行一个简单的条件开始。如果条件是true,语句的集合会重复执行知道条件变成false。

这里是while循环的普通形式:

while condition {
    statements
}

这个例子玩的是简单的蛇与梯子的游戏(也叫滑到与梯子):


游戏的规则如下:

  • 板子上有25个方格,目标是到达或者超过第25个方格。
  • 玩家从方块0开始,在板子左下角之外。
  • 每次切换,摇动一个六面的筛子并且移动那个数字的方格,顺着在上面标记了圆点箭头的水平路径
  • 如果你转到了梯子下面,爬上这个梯子
  • 如果到了蛇的头部,从蛇那里下来

游戏板子用一个Int类型的数组表示。它的尺寸以名为finalSquare的常量为基础,用来初始化一个数组同事也在例子中稍后检查胜利条件。因为玩家玩家从方块0开始板子,板子使用26个零初始化,不是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(3和11之间的差)。为了同意值和语句,一元操作符(+i)与一元操作符(-i)一起使用并且小于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!")

上面的例子使用了非常简单的方法来摇色子。替换了生成一个随机数,它用一个为0的diceRool值。每执行一遍while循环体,diceRoll加一并且检查它有没有变得太大。当它返回的值等于7时,筛子的值变得太大了并且重新设置为1.结果是一个diceRoll的值的序列,一般是1,2,3,4,5,6,1,2等等。

筛子摇完之后,玩家向前移动diceRoll个方格。筛子可以已经让玩家超过了25,这样的话游戏结束。对于这种情况,代码检查square小于board数组的count属性。如果square是有效的,存在board[square]中的值就会被添加到当前square的值中来使玩家向前或者向后移动任何梯子或者蛇。

如果没有执行检查,board[square]可能访问board数组边界之外的值,触发runtime错误。

当前while循环体执行然后结束,并且循环体的条件检查来查看循环是否再执行一遍。如果玩家已经移动到或超过25,循环体的条件得出false并且游戏结束。

一个while循环适合这种情况,因为游戏的长度在while循环打开时是不确定的。取而代之的是,循环会执行一直到满足一个特定的值。

Repeat-While

另一个while循环的变体,是repeat-while循环,首先执行一遍循环块,在考虑循环条件之前。然后继续重复循环知道条件是false。

swift中repeat-while循环和其他语言中的do-while循环相似

这里是repeat-while循环的一般的形式:

repeat {
    statements
} while condition

这里又是Snakes和Ladeers例子,用repeat-while循环而不是while循环。finalSquare,board,quare和diceRoll的值用和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,所以不可能因为上了一个梯子而赢得比赛。所以,在循环中先检查梯子和蛇是安全的。

在游戏开始,玩家在“quare zero”。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循环更好的适合这个游戏。square+=board[square]通常在循环的while条件符合square仍然在板子上时立即执行。这样移除了之前游戏描述的while循环版本中检查数组边界的需要。

条件语句(Conditional Statements)

在确切条件下经常需要执行不同片段的代码。当有错误时,你可能想运行额外的一段代码,或者当值太大或者太小时展现一段信息。想这样做,让你部分代码有条件地.

swift提供了两种方式添加条件语句到你的代码中:if语句和switch语句。一般,使用if语句来执行简单的用少量可能输出的条件。switch语句更适用于更复杂的有多重可能序列的条件并且对于模式匹配可以帮助选择合适代码分支来执行的情况更有用。

If

它的最简单的形式,if语句有一个单一的if条件。只有条件是true时执行一些的语句。

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

因为温度不是太冷也不是太热的时候不会触发if或者else if条件,没有信息打印。

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语句由多个可能的cases组成,每个whitch用case关键字开始。除了对比特定的值,swift为每个指定更复杂匹配模式提供了多种方式。这些选项稍后在这个章节介绍。

像if语句的主体,每个case是一个单独执行代码的分支。swift语句决定哪个分支要选择。这个过程是选择考虑的那个值。

每一个switch语句必需要详细。也就是,分析的类型的每一个可能的值必须要与switch的cases中的一个匹配。如果为每一个可能的值提供一个case不太合适,可以定义一个default case来覆盖没有明确处理的值。这个default case用default关键字标记,必需出现在最后。

这个例子使用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语句使用defaultcase来匹配除了a和z之外的字母。这个准备保证了switch语句是全面的。

没有默认全执行(No Implicit Fallthrough)

与c和Objective-C中的switch语句相比,swift中的switch语句没有每个case执行到底并默认进入下一个。而是一旦第一个匹配的switch case结束了整个switch语句就结束了,不需要明确的break语句。这使switch语句比C中的更安全更易于使用并且避免了错误的执行超过一个switch case。

swift中即使break不是必须的,你可以使用break来匹配忽略一个指定的case或者在case完成执行之前从匹配的case中跳出来。更多的细节,查看Break in a Switch Statement

每个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语句,这个switch语句没有同事匹配“a"和"A“。而是,它提出了一个编译错误,case”a“:没有包含任何可执行语句。这个方式避免了意外从一个case中穿过到另一个case中,而且构建了目的更明确的更安全的代码。

为了使用一个单一的case构建一个switch,匹配”a“和”A“,将两个值结合成为一个组合的case,使用逗号分隔。

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

为了可读性,组合的case也可以写在多行上。更多关于组合case的信息,查看Compound Cases

为了明确执行到一个特殊switch case的最后,使用fallthrough关键字,描述在Fallthrough

区间匹配(Interval Matching)

switch cases中的值可以检查他们包含在一个区间中。这个例子使用数字区间为任何大小的数字提供一个自然语言总数。

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语句之外。

Tuples

可以使用元祖测试同一个switch语句中多个值。元祖的每个元素可以和不同的值或者值的区间进行测试。另外,使用下划线,也就是通配符,匹配任何可能的值。

下面的例子用一个(x,y)点,作为一个类型为(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")
}
// Prints "(1, 1) is inside the box"


switch语句点在原点(0,0),在红色x轴,在橙色y轴,在中心店为原点的蓝色4*4框,或者框外。

不像C,swift允许多个switch cases来关注相同的一个或多个值。实际上,点(0,0)可以匹配例子中全部的四个cases。不过,如果多个匹配都是可能的,通常使用第一个匹配的case。点(0,0)会先匹配case(0,0),然后其他全部匹配的cases将会被忽略。

值绑定(Value Bindings)

一个switch case可以吧他匹配的值命名为临时的常量或者变量,为了在case的主体中使用。这个行为叫做值绑定,因为值限制在case的主体中常量或变量。

下面的例子用一个(x,y)的点,表示一个类型为(Int,Int)的元祖,并把它归类到后面的图形中:

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"


switch语句决定点是否在红色x轴上,在橙色y轴上,或者其他地方(不在两个轴上)。

三个cases声明了占位常量x和y,临时使用一个或者两个anotherPoint中的值。第一个case,case(let x,0)匹配任何y值是0的点并且将x的值分配给临时常量x。同样的,第二个case,case(0,let y),匹配任意的x值是0的点并且把点的y的值分配给临时常量y。

在临时常量声明之后,他们在case代码的块中使用。这里,他们用来打印点的分类。

这个switch语句没有default case。最后的case,case let(x,y),声明了一个有两个可以匹配任何值的常量的元祖。因为anotherPoint通常是两个值的元祖,这个case匹配全部可能保留的值,所以不需要一个default case来使switch语句全面。

Where

一个swift 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")
}
‘// Prints "(1, -1) is on the line x == -y"


switch语句决定一个点是否在x==y的绿色对角线上,在紫色x==-y的对角线上,或者都不在。

三个switch cases盛行了默认常量x和y,令时从yetAnotherPoint中使用了两个元祖的值。只有where从句的条件计算是true时switch case匹配point当前的值

如之前的例子一样,最后case匹配余留的全部的值,所以不需要default case来使switch语句全面。

组合case(Compound Case)

多个共享一个主题的switch cases可以通过在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 case匹配其他全部字母。

组合cases 也可以包含值绑定。组合case的全部模型需要有一样的值绑定的序列,并且每个值绑定需要从组合case中全部的模型中获取相同类型的值。这确保了,不论组合case的那个部分匹配了,主体代码都可以访问绑定的值并且这些值都有相同的类型。

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在两个模型中都是整型--一位置case的主体中的代码都可以访问到一个distance的值。

控制切换语句(Control Transfer Statements)

控制切换语句改变了代码中执行的顺序,通过从一段代码切换控制到另一个中。swift有5个控制切换语句:

  • continue
  • break
  • fallthrough
  • return
  • throw

continue,break,和fallthrough语句在下面描述。return语句在 Functions中描述,throw语句在Propagating Errors Using Throwing Functions中描述。

Continue

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"

只要匹配到了原因或者空格上面的代码调用continue关键字,使当前循环的遍历立即结束并且调到下一个循环的开始。

Break

break语句立即结束整个控制流的执行。当你想相较其他情况提前结束switch或者循环语句的执行时break语句可以用在一个switch或者loop语句中。

Break in a Loop Statement

当在循环语句中使用时,break立刻结束循环的执行并且将控制切换到循环闭括号后面的代码。没有更多的该循环当前遍历的代码执行,没有更多循环的遍历开始。

Break in a Switch Statement

挡在switch语句中使用时,break使switch语句立即结束它的执行并且将控制切换到switch语句的闭合大括号后的代码。

这个表现可以用来匹配和忽略一个或者多个switch语句中的cases。因为swift的switch语句是详尽的并且不允许空cases,有时候需要随心所欲地匹配或者忽略一个case使目的更明确。当想忽略case的整个主体时可以写break语句来实现。当那个case通过switch语句匹配时,case中的break语句立即结束switch语句的执行。

一个switch的只有注释的case会报编译错误。注释不是语句并且不会使switch的case被忽略。使用break语句来忽略switch的case。

下面的例子对Charachter进行switch并确定他是否用四种语言之一表示一个数字。为了简便,多个值写在一个switch case中。

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来确定他是不是数字1到4的Latin,Arabic,Chinese,或者Thai符号。如果找到了一个匹配,switch语句的cases之一将名为possibleIntegeValue的可选类型的Int?变量设置为合适的整型值。

在switch语句完成执行之后,例子使用可选绑定来确定是否发现了值。possibleIntegerValue变量因为是可选类型的性质会有一个默认初始化的值nil,所以可选绑定只有possibleIntegerValue被switch的前四个cases设置成真实的值时才会成功。

因为它对上面例子中列出每一个可能Character值没有实用价值,一个defaultcase处理了全部没有匹配的characters。这个default case不需要任何实现,所以它只写了一个break语句作为他的主体。一旦default case匹配了,break语句结束switch语句的执行,并且代码从if let语句继续执行。

Fallthrough

在swift中,switch语句不需要贯穿每个case的底部然后到下一个。也就是,一旦第一个匹配的case执行完了整个switch语句完成执行。比较而言,C需要你在每个switch case的尾部插入明确的break语句来防止穿过。避免默认穿过意思是swift switch语句比C中同等的语句更加简洁和可预测。

如果你需要C类型的贯穿表现,你可以使用fallthrough关键字选择在case-by-case基础上这种表现。下面的例子使用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."

这个例子声明了一个新的名为description的string变量并且给他一个初始化值。函数会使用switch语句计算intergerToDescribe的值。如果integerToDescribe的值时列表的数字之一,函数在description尾部拼接文本,记下那个数字是质数。使用fallthrough关键字进入default case。default case在描述的尾部增加额外的文本,switch语句是完成。

除非integerToDescribe的值在已知质数的列表里,它不会被switch 的第一个case匹配。因为没有其他的明确的cases,integerToDescribe匹配default case。

switch语句结束执行之后,数字的描述使用print(_:separator:terminator:)函数打印。在这个例子中,数字5正确定义为了一个质数。

fallthrough 关键字不会检查使执行继续的switch case的case条件。fallthrough关键字只是使代码继续执行下一个case(或者default case)块中的语句,就像C的标准switch语句表现。

标签语句

swift中,你在其他循环和条件语句中内嵌循环和条件语句来创建复杂的控制流结构。不过,循环和条件语句可以使用break语句来提前结束他们的执行。所以,有时候对明确那个你想用break语句来结束的循环或者条件语句是有用的。相似的,如果你有多个内嵌的循环,他可以用来明确continue语句应该影响哪个循环。

要实现这些目的,可以使用语句标签来标记一个循环语句或者条件语句,可以使用语句标签和break语句类结束标签语句的执行。对于循环语句,你可以使用标签语句和break或者continue语句来结束或者继续标签语句的执行。

标签语句通过在和语句的声明关键字同一行加一个标签来指明,跟着一个冒号。这里是这个语法对while循环的例子,但是对所有循环和switch语句的原则是一样的:

label name: while condition {
    statements
}

下面的例子使用break和continue语句和在这个章节之前看到的蛇和爬梯游戏的改写版本的标签while循环。这一次,游戏有另外的规则:

  • 要赢,必需准确停在方块25上。

如果一次特别的摇筛子让你超过了方块25,你必须重新摇知道你要到了准确的你需要停在方块25的数字。

板子游戏和之前一样。


finalSquare,board,square,和diceRoll的值和之前一样的方式初始化:

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循环的遍历并且开始循环的下一次循环。
  • 在其他所有的cases中,掷骰子是有效的移动。玩家向前移动diceRoll个方块,而且游戏逻辑检查任何蛇和梯子。循环结束,控制返回到while条件判断是否需要另一次转变。
如果上面的break语句没有使用gameLoop标签,它就会跳出switch语句,不是while语句。使用gameLoop标签式哪个控制流要结束变得很清楚。当调用continue gameLoop来跳到循环的下一次遍历时没有直接的需求使用gameLoop标签。在游戏中只有一个循环,所以在哪个循环受到continue语句影响时没有冲突。不过,使用gameLoop标签和continue statement没有害处。和break语句一起使用标签这样做非常流畅并且使游戏的逻辑清晰可读和可理解。

提前退出(Early Exit)

一个guard语句,像if语句,执行状态依靠表达式的布尔值。使用guard语句为了使guard语句后的代码执行需要条件是true。不像语句if,一个guard语句通常有else从句--else从句中的代码在条件不是true时执行。

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出现的余下部分的代码块中都是可用的。

如果条件没有满足,else分支中的代码执行。分支必需切换控制流退出guard语句出现的代码块。可以使用控制切换语句return,break,continue,或者throw来做这些事,例如fatalError(_:file:line:)。

对需求使用guard语句提高代码可读性,对比于使用if语句执行相同的检查。使你写的特别的代码不需要包在else块中,让你可以紧挨需求保留处理与需求冲突的代码。

检查API可用性(Checking API Availability)

swift内建查看API可用性的支持,确保不会意外使用在给定部署目标上不可以使用的APIs。

编译器使用SDK中可用的信息来验证你代码中使用的APIs在你的工程指定的部署目标上是可用的。如果你尝试使用不可用的API,swift会报编译错误。

在if或者guard语句中使用可用条件来有条件地执行一块代码,取决于你想使用的APIs在运行期是否可用。当它验证代码块中的APIs可以用时编译器使用可用条件中的信息。

上面在IOS中指定的可用条件,if语句的主体只在IOS10和更新的中执行,在macOS中,只在macOS10.12和更新的。最后的参数,*,是必须的而且指明任何其他平台,if的主体在你的目标指定的最小部署目标上执行。

在它的普通形式中,可用条件用了一个平台名字和版本的列表。使用像iOS,macOS,watchOS,和tvOS的平台名称--全部的列表,查看Declaration Attributes。除了指定像iOS8或者macOS10.10主版本号,你可以指定最小的版本号像iOS11.2.6和macOS10.13.3.

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