1.0 翻譯:vclwei, coverxit, NicePiao 校對(duì):coverxit, stanzhai
2.0 翻譯+校對(duì):JackAlan
2.2 翻譯:LinusLing 校對(duì):SketchK
3.0 翻譯:Realank 2016-09-13 3.0.1,shanks,2016-11-12
3.1 翻譯:qhd 2017-04-17
4.0 翻譯:kemchenj 2017-09-21
4.1 翻譯+校對(duì):mylittleswift
本頁(yè)包含內(nèi)容:
Swift 提供了多種流程控制結(jié)構(gòu),包括可以多次執(zhí)行任務(wù)的 while 循環(huán),基于特定條件選擇執(zhí)行不同代碼分支的 if、guard 和 switch 語(yǔ)句,還有控制流程跳轉(zhuǎn)到其他代碼位置的 break 和 continue 語(yǔ)句。
Swift 還提供了 for-in 循環(huán),用來(lái)更簡(jiǎn)單地遍歷數(shù)組(Array),字典(Dictionary),區(qū)間(Range),字符串(String)和其他序列類型。
Swift 的 switch 語(yǔ)句比 C 語(yǔ)言中更加強(qiáng)大。case 還可以匹配很多不同的模式,包括范圍匹配,元組(tuple)和特定類型匹配。switch 語(yǔ)句的 case 中匹配的值可以聲明為臨時(shí)常量或變量,在 case 作用域內(nèi)使用,也可以配合 where 來(lái)描述更復(fù)雜的匹配條件。
你可以使用 for-in 循環(huán)來(lái)遍歷一個(gè)集合中的所有元素,例如數(shù)組中的元素、范圍內(nèi)的數(shù)字或者字符串中的字符。
以下例子使用 for-in 遍歷一個(gè)數(shù)組所有元素:
let names = ["Anna", "Alex", "Brian", "Jack"]
for name in names {
print("Hello, \(name)!")
}
// Hello, Anna!
// Hello, Alex!
// Hello, Brian!
// Hello, Jack!
你也可以通過(guò)遍歷一個(gè)字典來(lái)訪問(wèn)它的鍵值對(duì)。遍歷字典時(shí),字典的每項(xiàng)元素會(huì)以 (key, value) 元組的形式返回,你可以在 for-in 循環(huán)中使用顯式的常量名稱來(lái)解讀 (key, value) 元組。下面的例子中,字典的鍵聲明會(huì)為 animalName 常量,字典的值會(huì)聲明為 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
字典的內(nèi)容理論上是無(wú)序的,遍歷元素時(shí)的順序是無(wú)法確定的。將元素插入字典的順序并不會(huì)決定它們被遍歷的順序。關(guān)于數(shù)組和字典的細(xì)節(jié),參見(jiàn)集合類型。
for-in 循環(huán)還可以使用數(shù)字范圍。下面的例子用來(lái)輸出乘法表的一部分內(nèi)容:
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
例子中用來(lái)進(jìn)行遍歷的元素是使用閉區(qū)間操作符(...)表示的從 1 到 5 的數(shù)字區(qū)間。index 被賦值為閉區(qū)間中的第一個(gè)數(shù)字(1),然后循環(huán)中的語(yǔ)句被執(zhí)行一次。在本例中,這個(gè)循環(huán)只包含一個(gè)語(yǔ)句,用來(lái)輸出當(dāng)前 index 值所對(duì)應(yīng)的乘 5 乘法表的結(jié)果。該語(yǔ)句執(zhí)行后,index 的值被更新為閉區(qū)間中的第二個(gè)數(shù)字(2),之后 print(_:separator:terminator:) 函數(shù)會(huì)再執(zhí)行一次。整個(gè)過(guò)程會(huì)進(jìn)行到閉區(qū)間結(jié)尾為止。
上面的例子中,index 是一個(gè)每次循環(huán)遍歷開(kāi)始時(shí)被自動(dòng)賦值的常量。這種情況下,index 在使用前不需要聲明,只需要將它包含在循環(huán)的聲明中,就可以對(duì)其進(jìn)行隱式聲明,而無(wú)需使用 let 關(guān)鍵字聲明。
如果你不需要區(qū)間序列內(nèi)每一項(xiàng)的值,你可以使用下劃線(_)替代變量名來(lái)忽略這個(gè)值:
let base = 3
let power = 10
var answer = 1
for _ in 1...power {
answer *= base
}
print("\(base) to the power of \(power) is \(answer)")
// 輸出 "3 to the power of 10 is 59049"
這個(gè)例子計(jì)算 base 這個(gè)數(shù)的 power 次冪(本例中,是 3 的 10 次冪),從 1(3 的 0 次冪)開(kāi)始做 3 的乘法, 進(jìn)行 10 次,使用 1 到 10 的閉區(qū)間循環(huán)。這個(gè)計(jì)算并不需要知道每一次循環(huán)中計(jì)數(shù)器具體的值,只需要執(zhí)行了正確的循環(huán)次數(shù)即可。下劃線符號(hào) _ (替代循環(huán)中的變量)能夠忽略當(dāng)前值,并且不提供循環(huán)遍歷時(shí)對(duì)值的訪問(wèn)。
在某些情況下,你可能不想使用閉區(qū)間,包括兩個(gè)端點(diǎn)。想象一下,你在一個(gè)手表上繪制分鐘的刻度線??偣?60 個(gè)刻度,從 0 分開(kāi)始。使用半開(kāi)區(qū)間運(yùn)算符(..<)來(lái)表示一個(gè)左閉右開(kāi)的區(qū)間。有關(guān)區(qū)間的更多信息,請(qǐng)參閱區(qū)間運(yùn)算符。
let minutes = 60
for tickMark in 0..<minutes {
// 每一分鐘都渲染一個(gè)刻度線(60次)
}
一些用戶可能在其 UI 中可能需要較少的刻度。他們可以每5分鐘作為一個(gè)刻度。使用 stride(from:to:by:) 函數(shù)跳過(guò)不需要的標(biāo)記。
let minuteInterval = 5
for tickMark in stride(from: 0, to: minutes, by: minuteInterval) {
// 每5分鐘渲染一個(gè)刻度線(0, 5, 10, 15 ... 45, 50, 55)
}
可以在閉區(qū)間使用 stride(from:through:by:) 起到同樣作用:
let hours = 12
let hourInterval = 3
for tickMark in stride(from: 3, through: hours, by: hourInterval) {
// 每3小時(shí)渲染一個(gè)刻度線(3, 6, 9, 12)
}
while 循環(huán)會(huì)一直運(yùn)行一段語(yǔ)句直到條件變成 false。這類循環(huán)適合使用在第一次迭代前,迭代次數(shù)未知的情況下。Swift 提供兩種 while 循環(huán)形式:
while 循環(huán),每次在循環(huán)開(kāi)始時(shí)計(jì)算條件是否符合;repeat-while 循環(huán),每次在循環(huán)結(jié)束時(shí)計(jì)算條件是否符合。while 循環(huán)從計(jì)算一個(gè)條件開(kāi)始。如果條件為 true,會(huì)重復(fù)運(yùn)行一段語(yǔ)句,直到條件變?yōu)?false。
下面是 while 循環(huán)的一般格式:
while condition {
statements
}
下面的例子來(lái)玩一個(gè)叫做蛇和梯子(也叫做滑道和梯子)的小游戲:

游戲的規(guī)則如下:
游戲盤面可以使用一個(gè) Int 數(shù)組來(lái)表達(dá)。數(shù)組的長(zhǎng)度由一個(gè) finalSquare 常量?jī)?chǔ)存,用來(lái)初始化數(shù)組和檢測(cè)最終勝利條件。游戲盤面由 26 個(gè) Int 0 值初始化,而不是 25 個(gè)(由 0 到 25,一共 26 個(gè)):
let finalSquare = 25
var board = [Int](repeating: 0, count: finalSquare + 1)
一些方格被設(shè)置成特定的值來(lái)表示有蛇或者梯子。梯子底部的方格是一個(gè)正值,使你可以向上移動(dòng),蛇頭處的方格是一個(gè)負(fù)值,會(huì)讓你向下移動(dòng):
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 號(hào)方格是梯子的底部,會(huì)讓你向上移動(dòng)到 11 號(hào)方格,我們使用 board[03] 等于 +08(來(lái)表示 11 和 3 之間的差值)。為了對(duì)齊語(yǔ)句,這里使用了一元正運(yùn)算符(+i)和一元負(fù)運(yùn)算符(-i),并且小于 10 的數(shù)字都使用 0 補(bǔ)齊(這些語(yǔ)法的技巧不是必要的,只是為了讓代碼看起來(lái)更加整潔)。
玩家由左下角空白處編號(hào)為 0 的方格開(kāi)始游戲。玩家第一次擲骰子后才會(huì)進(jìn)入游戲盤面:
var square = 0
var diceRoll = 0
while square < finalSquare {
// 擲骰子
diceRoll += 1
if diceRoll == 7 { diceRoll = 1 }
// 根據(jù)點(diǎn)數(shù)移動(dòng)
square += diceRoll
if square < board.count {
// 如果玩家還在棋盤上,順著梯子爬上去或者順著蛇滑下去
square += board[square]
}
}
print("Game over!")
本例中使用了最簡(jiǎn)單的方法來(lái)模擬擲骰子。diceRoll 的值并不是一個(gè)隨機(jī)數(shù),而是以 0 為初始值,之后每一次 while 循環(huán),diceRoll 的值增加 1 ,然后檢測(cè)是否超出了最大值。當(dāng) diceRoll 的值等于 7 時(shí),就超過(guò)了骰子的最大值,會(huì)被重置為 1。所以 diceRoll 的取值順序會(huì)一直是 1,2,3,4,5,6,1,2 等。
擲完骰子后,玩家向前移動(dòng) diceRoll 個(gè)方格,如果玩家移動(dòng)超過(guò)了第 25 個(gè)方格,這個(gè)時(shí)候游戲?qū)?huì)結(jié)束,為了應(yīng)對(duì)這種情況,代碼會(huì)首先判斷 square 的值是否小于 board 的 count 屬性,只有小于才會(huì)在 board[square] 上增加 square,來(lái)向前或向后移動(dòng)(遇到了梯子或者蛇)。
注意
如果沒(méi)有這個(gè)檢測(cè)(
square < board.count),board[square]可能會(huì)越界訪問(wèn)board數(shù)組,導(dǎo)致錯(cuò)誤。
當(dāng)本輪 while 循環(huán)運(yùn)行完畢,會(huì)再檢測(cè)循環(huán)條件是否需要再運(yùn)行一次循環(huán)。如果玩家移動(dòng)到或者超過(guò)第 25 個(gè)方格,循環(huán)條件結(jié)果為 false,此時(shí)游戲結(jié)束。
while 循環(huán)比較適合本例中的這種情況,因?yàn)樵?while 循環(huán)開(kāi)始時(shí),我們并不知道游戲要跑多久,只有在達(dá)成指定條件時(shí)循環(huán)才會(huì)結(jié)束。
while 循環(huán)的另外一種形式是 repeat-while,它和 while 的區(qū)別是在判斷循環(huán)條件之前,先執(zhí)行一次循環(huán)的代碼塊。然后重復(fù)循環(huán)直到條件為 false。
注意
Swift 語(yǔ)言的
repeat-while循環(huán)和其他語(yǔ)言中的do-while循環(huán)是類似的。
下面是 repeat-while 循環(huán)的一般格式:
repeat {
statements
} while condition
還是蛇和梯子的游戲,使用 repeat-while 循環(huán)來(lái)替代 while 循環(huán)。finalSquare、board、square 和 diceRoll 的值初始化同 while 循環(huán)時(shí)一樣:
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
repeat-while 的循環(huán)版本,循環(huán)中第一步就需要去檢測(cè)是否在梯子或者蛇的方塊上。沒(méi)有梯子會(huì)讓玩家直接上到第 25 個(gè)方格,所以玩家不會(huì)通過(guò)梯子直接贏得游戲。這樣在循環(huán)開(kāi)始時(shí)先檢測(cè)是否踩在梯子或者蛇上是安全的。
游戲開(kāi)始時(shí),玩家在第 0 個(gè)方格上,board[0] 一直等于 0, 不會(huì)有什么影響:
repeat {
// 順著梯子爬上去或者順著蛇滑下去
square += board[square]
// 擲骰子
diceRoll += 1
if diceRoll == 7 { diceRoll = 1 }
// 根據(jù)點(diǎn)數(shù)移動(dòng)
square += diceRoll
} while square < finalSquare
print("Game over!")
檢測(cè)完玩家是否踩在梯子或者蛇上之后,開(kāi)始擲骰子,然后玩家向前移動(dòng) diceRoll 個(gè)方格,本輪循環(huán)結(jié)束。
循環(huán)條件(while square < finalSquare)和 while 方式相同,但是只會(huì)在循環(huán)結(jié)束后進(jìn)行計(jì)算。在這個(gè)游戲中,repeat-while 表現(xiàn)得比 while 循環(huán)更好。repeat-while 方式會(huì)在條件判斷 square 沒(méi)有超出后直接運(yùn)行 square += board[square],這種方式可以比起前面 while 循環(huán)的版本,可以省去數(shù)組越界的檢查。
根據(jù)特定的條件執(zhí)行特定的代碼通常是十分有用的。當(dāng)錯(cuò)誤發(fā)生時(shí),你可能想運(yùn)行額外的代碼;或者,當(dāng)值太大或太小時(shí),向用戶顯示一條消息。要實(shí)現(xiàn)這些功能,你就需要使用條件語(yǔ)句。
Swift 提供兩種類型的條件語(yǔ)句:if 語(yǔ)句和 switch 語(yǔ)句。通常,當(dāng)條件較為簡(jiǎn)單且可能的情況很少時(shí),使用 if 語(yǔ)句。而 switch 語(yǔ)句更適用于條件較復(fù)雜、有更多排列組合的時(shí)候。并且 switch 在需要用到模式匹配(pattern-matching)的情況下會(huì)更有用。
if 語(yǔ)句最簡(jiǎn)單的形式就是只包含一個(gè)條件,只有該條件為 true 時(shí),才執(zhí)行相關(guān)代碼:
var temperatureInFahrenheit = 30
if temperatureInFahrenheit <= 32 {
print("It's very cold. Consider wearing a scarf.")
}
// 輸出 "It's very cold. Consider wearing a scarf."
上面的例子會(huì)判斷溫度是否小于等于 32 華氏度(水的冰點(diǎn))。如果是,則打印一條消息;否則,不打印任何消息,繼續(xù)執(zhí)行 if 塊后面的代碼。
當(dāng)然,if 語(yǔ)句允許二選一執(zhí)行,叫做 else 從句。也就是當(dāng)條件為 false 時(shí),執(zhí)行 else 語(yǔ)句:
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.")
}
// 輸出 "It's not that cold. Wear a t-shirt."
顯然,這兩條分支中總有一條會(huì)被執(zhí)行。由于溫度已升至 40 華氏度,不算太冷,沒(méi)必要再圍圍巾。因此,else 分支就被觸發(fā)了。
你可以把多個(gè) if 語(yǔ)句鏈接在一起,來(lái)實(shí)現(xiàn)更多分支:
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.")
}
// 輸出 "It's really warm. Don't forget to wear sunscreen."
在上面的例子中,額外的 if 語(yǔ)句用于判斷是不是特別熱。而最后的 else 語(yǔ)句被保留了下來(lái),用于打印既不冷也不熱時(shí)的消息。
實(shí)際上,當(dāng)不需要完整判斷情況的時(shí)候,最后的 else 語(yǔ)句是可選的:
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.")
}
在這個(gè)例子中,由于既不冷也不熱,所以不會(huì)觸發(fā) if 或 else if 分支,也就不會(huì)打印任何消息。
switch 語(yǔ)句會(huì)嘗試把某個(gè)值與若干個(gè)模式(pattern)進(jìn)行匹配。根據(jù)第一個(gè)匹配成功的模式,switch 語(yǔ)句會(huì)執(zhí)行對(duì)應(yīng)的代碼。當(dāng)有可能的情況較多時(shí),通常用 switch 語(yǔ)句替換 if 語(yǔ)句。
switch 語(yǔ)句最簡(jiǎn)單的形式就是把某個(gè)值與一個(gè)或若干個(gè)相同類型的值作比較:
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 語(yǔ)句由多個(gè) case 構(gòu)成,每個(gè)由 case 關(guān)鍵字開(kāi)始。為了匹配某些更特定的值,Swift 提供了幾種方法來(lái)進(jìn)行更復(fù)雜的模式匹配,這些模式將在本節(jié)的稍后部分提到。
與 if 語(yǔ)句類似,每一個(gè) case 都是代碼執(zhí)行的一條分支。switch 語(yǔ)句會(huì)決定哪一條分支應(yīng)該被執(zhí)行,這個(gè)流程被稱作根據(jù)給定的值切換(switching)。
switch 語(yǔ)句必須是完備的。這就是說(shuō),每一個(gè)可能的值都必須至少有一個(gè) case 分支與之對(duì)應(yīng)。在某些不可能涵蓋所有值的情況下,你可以使用默認(rèn)(default)分支來(lái)涵蓋其它所有沒(méi)有對(duì)應(yīng)的值,這個(gè)默認(rèn)分支必須在 switch 語(yǔ)句的最后面。
下面的例子使用 switch 語(yǔ)句來(lái)匹配一個(gè)名為 someCharacter 的小寫(xiě)字符:
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")
}
// 輸出 "The last letter of the alphabet"
在這個(gè)例子中,第一個(gè) case 分支用于匹配第一個(gè)英文字母 a,第二個(gè) case 分支用于匹配最后一個(gè)字母 z。
因?yàn)?switch 語(yǔ)句必須有一個(gè) case 分支用于覆蓋所有可能的字符,而不僅僅是所有的英文字母,所以 switch 語(yǔ)句使用 default 分支來(lái)匹配除了 a 和 z 外的所有值,這個(gè)分支保證了 swith 語(yǔ)句的完備性。
與 C 和 Objective-C 中的 switch 語(yǔ)句不同,在 Swift 中,當(dāng)匹配的 case 分支中的代碼執(zhí)行完畢后,程序會(huì)終止 switch 語(yǔ)句,而不會(huì)繼續(xù)執(zhí)行下一個(gè) case 分支。這也就是說(shuō),不需要在 case 分支中顯式地使用 break 語(yǔ)句。這使得 switch 語(yǔ)句更安全、更易用,也避免了因忘記寫(xiě) break 語(yǔ)句而產(chǎn)生的錯(cuò)誤。
注意
雖然在 Swift 中
break不是必須的,但你依然可以在 case 分支中的代碼執(zhí)行完畢前使用break跳出,詳情請(qǐng)參見(jiàn)Switch 語(yǔ)句中的 break。
每一個(gè) case 分支都必須包含至少一條語(yǔ)句。像下面這樣書(shū)寫(xiě)代碼是無(wú)效的,因?yàn)榈谝粋€(gè) case 分支是空的:
let anotherCharacter: Character = "a"
switch anotherCharacter {
case "a": // 無(wú)效,這個(gè)分支下面沒(méi)有語(yǔ)句
case "A":
print("The letter A")
default:
print("Not the letter A")
}
// 這段代碼會(huì)報(bào)編譯錯(cuò)誤
不像 C 語(yǔ)言里的 switch 語(yǔ)句,在 Swift 中,switch 語(yǔ)句不會(huì)一起匹配 "a" 和 "A"。相反的,上面的代碼會(huì)引起編譯期錯(cuò)誤:case "a": 不包含任何可執(zhí)行語(yǔ)句——這就避免了意外地從一個(gè) case 分支貫穿到另外一個(gè),使得代碼更安全、也更直觀。
為了讓單個(gè) case 同時(shí)匹配 a 和 A,可以將這個(gè)兩個(gè)值組合成一個(gè)復(fù)合匹配,并且用逗號(hào)分開(kāi):
let anotherCharacter: Character = "a"
switch anotherCharacter {
case "a", "A":
print("The letter A")
default:
print("Not the letter A")
}
// 輸出 "The letter A
為了可讀性,符合匹配可以寫(xiě)成多行形式,詳情請(qǐng)參考復(fù)合匹配
注意
如果想要顯式貫穿 case 分支,請(qǐng)使用
fallthrough語(yǔ)句,詳情請(qǐng)參考貫穿。
case 分支的模式也可以是一個(gè)值的區(qū)間。下面的例子展示了如何使用區(qū)間匹配來(lái)輸出任意數(shù)字對(duì)應(yīng)的自然語(yǔ)言格式:
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).")
// 輸出 "There are dozens of moons orbiting Saturn."
在上例中,approximateCount 在一個(gè) switch 聲明中被評(píng)估。每一個(gè) case 都與之進(jìn)行比較。因?yàn)?approximateCount 落在了 12 到 100 的區(qū)間,所以 naturalCount 等于 "dozens of" 值,并且此后的執(zhí)行跳出了 switch 語(yǔ)句。
我們可以使用元組在同一個(gè) switch 語(yǔ)句中測(cè)試多個(gè)值。元組中的元素可以是值,也可以是區(qū)間。另外,使用下劃線(_)來(lái)匹配所有可能的值。
下面的例子展示了如何使用一個(gè) (Int, Int) 類型的元組來(lái)分類下圖中的點(diǎn) (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")
}
// 輸出 "(1, 1) is inside the box"

在上面的例子中,switch 語(yǔ)句會(huì)判斷某個(gè)點(diǎn)是否是原點(diǎn) (0, 0),是否在紅色的 x 軸上,是否在橘黃色的 y 軸上,是否在一個(gè)以原點(diǎn)為中心的4x4的藍(lán)色矩形里,或者在這個(gè)矩形外面。
不像 C 語(yǔ)言,Swift 允許多個(gè) case 匹配同一個(gè)值。實(shí)際上,在這個(gè)例子中,點(diǎn) (0, 0)可以匹配所有四個(gè) case。但是,如果存在多個(gè)匹配,那么只會(huì)執(zhí)行第一個(gè)被匹配到的 case 分支。考慮點(diǎn) (0, 0)會(huì)首先匹配 case (0, 0),因此剩下的能夠匹配的分支都會(huì)被忽視掉。
case 分支允許將匹配的值聲明為臨時(shí)常量或變量,并且在 case 分支體內(nèi)使用 —— 這種行為被稱為值綁定(value binding),因?yàn)槠ヅ涞闹翟?case 分支體內(nèi),與臨時(shí)的常量或變量綁定。
下面的例子將下圖中的點(diǎn) (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))")
}
// 輸出 "on the x-axis with an x value of 2"

在上面的例子中,switch 語(yǔ)句會(huì)判斷某個(gè)點(diǎn)是否在紅色的 x 軸上,是否在橘黃色的 y 軸上,或者不在坐標(biāo)軸上。
這三個(gè) case 都聲明了常量 x 和 y 的占位符,用于臨時(shí)獲取元組 anotherPoint 的一個(gè)或兩個(gè)值。第一個(gè) case ——case (let x, 0) 將匹配一個(gè)縱坐標(biāo)為 0 的點(diǎn),并把這個(gè)點(diǎn)的橫坐標(biāo)賦給臨時(shí)的常量 x。類似的,第二個(gè) case ——case (0, let y) 將匹配一個(gè)橫坐標(biāo)為 0 的點(diǎn),并把這個(gè)點(diǎn)的縱坐標(biāo)賦給臨時(shí)的常量 y。
一旦聲明了這些臨時(shí)的常量,它們就可以在其對(duì)應(yīng)的 case 分支里使用。在這個(gè)例子中,它們用于打印給定點(diǎn)的類型。
請(qǐng)注意,這個(gè) switch 語(yǔ)句不包含默認(rèn)分支。這是因?yàn)樽詈笠粋€(gè) case ——case let(x, y) 聲明了一個(gè)可以匹配余下所有值的元組。這使得 switch 語(yǔ)句已經(jīng)完備了,因此不需要再書(shū)寫(xiě)默認(rèn)分支。
case 分支的模式可以使用 where 語(yǔ)句來(lái)判斷額外的條件。
下面的例子把下圖中的點(diǎn) (x, y)進(jìn)行了分類:
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")
}
// 輸出 "(1, -1) is on the line x == -y"

在上面的例子中,switch 語(yǔ)句會(huì)判斷某個(gè)點(diǎn)是否在綠色的對(duì)角線 x == y 上,是否在紫色的對(duì)角線 x == -y 上,或者不在對(duì)角線上。
這三個(gè) case 都聲明了常量 x 和 y 的占位符,用于臨時(shí)獲取元組 yetAnotherPoint 的兩個(gè)值。這兩個(gè)常量被用作 where 語(yǔ)句的一部分,從而創(chuàng)建一個(gè)動(dòng)態(tài)的過(guò)濾器(filter)。當(dāng)且僅當(dāng) where 語(yǔ)句的條件為 true 時(shí),匹配到的 case 分支才會(huì)被執(zhí)行。
就像是值綁定中的例子,由于最后一個(gè) case 分支匹配了余下所有可能的值,switch 語(yǔ)句就已經(jīng)完備了,因此不需要再書(shū)寫(xiě)默認(rèn)分支。
當(dāng)多個(gè)條件可以使用同一種方法來(lái)處理時(shí),可以將這幾種可能放在同一個(gè) case 后面,并且用逗號(hào)隔開(kāi)。當(dāng) case 后面的任意一種模式匹配的時(shí)候,這條分支就會(huì)被匹配。并且,如果匹配列表過(guò)長(zhǎng),還可以分行書(shū)寫(xiě):
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")
}
// 輸出 "e is a vowel"
這個(gè) switch 語(yǔ)句中的第一個(gè) case,匹配了英語(yǔ)中的五個(gè)小寫(xiě)元音字母。相似的,第二個(gè) case 匹配了英語(yǔ)中所有的小寫(xiě)輔音字母。最終,default 分支匹配了其它所有字符。
復(fù)合匹配同樣可以包含值綁定。復(fù)合匹配里所有的匹配模式,都必須包含相同的值綁定。并且每一個(gè)綁定都必須獲取到相同類型的值。這保證了,無(wú)論復(fù)合匹配中的哪個(gè)模式發(fā)生了匹配,分支體內(nèi)的代碼,都能獲取到綁定的值,并且綁定的值都有一樣的類型。
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")
}
// 輸出 "On an axis, 9 from the origin"
上面的 case 有兩個(gè)模式:(let distance, 0) 匹配了在 x 軸上的值,(0, let distance) 匹配了在 y 軸上的值。兩個(gè)模式都綁定了 distance,并且 distance 在兩種模式下,都是整型——這意味著分支體內(nèi)的代碼,只要 case 匹配,都可以獲取到 distance 值
控制轉(zhuǎn)移語(yǔ)句改變你代碼的執(zhí)行順序,通過(guò)它可以實(shí)現(xiàn)代碼的跳轉(zhuǎn)。Swift 有五種控制轉(zhuǎn)移語(yǔ)句:
continuebreakfallthroughreturnthrow我們將會(huì)在下面討論 continue、break 和 fallthrough 語(yǔ)句。return 語(yǔ)句將會(huì)在函數(shù)章節(jié)討論,throw 語(yǔ)句會(huì)在錯(cuò)誤拋出章節(jié)討論。
continue 語(yǔ)句告訴一個(gè)循環(huán)體立刻停止本次循環(huán),重新開(kāi)始下次循環(huán)。就好像在說(shuō)“本次循環(huán)我已經(jīng)執(zhí)行完了”,但是并不會(huì)離開(kāi)整個(gè)循環(huán)體。
下面的例子把一個(gè)小寫(xiě)字符串中的元音字母和空格字符移除,生成了一個(gè)含義模糊的短句:
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)
// 輸出 "grtmndsthnklk"
在上面的代碼中,只要匹配到元音字母或者空格字符,就調(diào)用 continue 語(yǔ)句,使本次循環(huán)結(jié)束,重新開(kāi)始下次循環(huán)。這種行為使 switch 匹配到元音字母和空格字符時(shí)不做處理,而不是讓每一個(gè)匹配到的字符都被打印。
break 語(yǔ)句會(huì)立刻結(jié)束整個(gè)控制流的執(zhí)行。break 可以在 switch 或循環(huán)語(yǔ)句中使用,用來(lái)提前結(jié)束 switch 或循環(huán)語(yǔ)句。
當(dāng)在一個(gè)循環(huán)體中使用 break 時(shí),會(huì)立刻中斷該循環(huán)體的執(zhí)行,然后跳轉(zhuǎn)到表示循環(huán)體結(jié)束的大括號(hào)(})后的第一行代碼。不會(huì)再有本次循環(huán)的代碼被執(zhí)行,也不會(huì)再有下次的循環(huán)產(chǎn)生。
當(dāng)在一個(gè) switch 代碼塊中使用 break 時(shí),會(huì)立即中斷該 switch 代碼塊的執(zhí)行,并且跳轉(zhuǎn)到表示 switch 代碼塊結(jié)束的大括號(hào)(})后的第一行代碼。
這種特性可以被用來(lái)匹配或者忽略一個(gè)或多個(gè)分支。因?yàn)?Swift 的 switch 需要包含所有的分支而且不允許有為空的分支,有時(shí)為了使你的意圖更明顯,需要特意匹配或者忽略某個(gè)分支。那么當(dāng)你想忽略某個(gè)分支時(shí),可以在該分支內(nèi)寫(xiě)上 break 語(yǔ)句。當(dāng)那個(gè)分支被匹配到時(shí),分支內(nèi)的 break 語(yǔ)句立即結(jié)束 switch 代碼塊。
注意
當(dāng)一個(gè)
switch分支僅僅包含注釋時(shí),會(huì)被報(bào)編譯時(shí)錯(cuò)誤。注釋不是代碼語(yǔ)句而且也不能讓switch分支達(dá)到被忽略的效果。你應(yīng)該使用break來(lái)忽略某個(gè)分支。
下面的例子通過(guò) switch 來(lái)判斷一個(gè) Character 值是否代表下面四種語(yǔ)言之一。為了簡(jiǎn)潔,多個(gè)值被包含在了同一個(gè)分支情況中。
let numberSymbol: Character = "三" // 簡(jiǎn)體中文里的數(shù)字 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).")
}
// 輸出 "The integer value of 三 is 3."
這個(gè)例子檢查 numberSymbol 是否是拉丁,阿拉伯,中文或者泰語(yǔ)中的 1 到 4 之一。如果被匹配到,該 switch 分支語(yǔ)句給 Int? 類型變量 possibleIntegerValue 設(shè)置一個(gè)整數(shù)值。
當(dāng) switch 代碼塊執(zhí)行完后,接下來(lái)的代碼通過(guò)使用可選綁定來(lái)判斷 possibleIntegerValue 是否曾經(jīng)被設(shè)置過(guò)值。因?yàn)槭强蛇x類型的緣故,possibleIntegerValue 有一個(gè)隱式的初始值 nil,所以僅僅當(dāng) possibleIntegerValue 曾被 switch 代碼塊的前四個(gè)分支中的某個(gè)設(shè)置過(guò)一個(gè)值時(shí),可選的綁定才會(huì)被判定為成功。
在上面的例子中,想要把 Character 所有的的可能性都枚舉出來(lái)是不現(xiàn)實(shí)的,所以使用 default 分支來(lái)包含所有上面沒(méi)有匹配到字符的情況。由于這個(gè) default 分支不需要執(zhí)行任何動(dòng)作,所以它只寫(xiě)了一條 break 語(yǔ)句。一旦落入到 default 分支中后,break 語(yǔ)句就完成了該分支的所有代碼操作,代碼繼續(xù)向下,開(kāi)始執(zhí)行 if let 語(yǔ)句。
在 Swift 里,switch 語(yǔ)句不會(huì)從上一個(gè) case 分支跳轉(zhuǎn)到下一個(gè) case 分支中。相反,只要第一個(gè)匹配到的 case 分支完成了它需要執(zhí)行的語(yǔ)句,整個(gè) switch 代碼塊完成了它的執(zhí)行。相比之下,C 語(yǔ)言要求你顯式地插入 break 語(yǔ)句到每個(gè) case 分支的末尾來(lái)阻止自動(dòng)落入到下一個(gè) case 分支中。Swift 的這種避免默認(rèn)落入到下一個(gè)分支中的特性意味著它的 switch 功能要比 C 語(yǔ)言的更加清晰和可預(yù)測(cè),可以避免無(wú)意識(shí)地執(zhí)行多個(gè) case 分支從而引發(fā)的錯(cuò)誤。
如果你確實(shí)需要 C 風(fēng)格的貫穿的特性,你可以在每個(gè)需要該特性的 case 分支中使用 fallthrough 關(guān)鍵字。下面的例子使用 fallthrough 來(lái)創(chuàng)建一個(gè)數(shù)字的描述語(yǔ)句。
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)
// 輸出 "The number 5 is a prime number, and also an integer."
這個(gè)例子定義了一個(gè) String 類型的變量 description 并且給它設(shè)置了一個(gè)初始值。函數(shù)使用 switch 邏輯來(lái)判斷 integerToDescribe 變量的值。當(dāng) integerToDescribe 的值屬于列表中的質(zhì)數(shù)之一時(shí),該函數(shù)在 description 后添加一段文字,來(lái)表明這個(gè)數(shù)字是一個(gè)質(zhì)數(shù)。然后它使用 fallthrough 關(guān)鍵字來(lái)“貫穿”到 default 分支中。default 分支在 description 的最后添加一段額外的文字,至此 switch 代碼塊執(zhí)行完了。
如果 integerToDescribe 的值不屬于列表中的任何質(zhì)數(shù),那么它不會(huì)匹配到第一個(gè) switch 分支。而這里沒(méi)有其他特別的分支情況,所以 integerToDescribe 匹配到 default 分支中。
當(dāng) switch 代碼塊執(zhí)行完后,使用 print(_:separator:terminator:) 函數(shù)打印該數(shù)字的描述。在這個(gè)例子中,數(shù)字 5 被準(zhǔn)確的識(shí)別為了一個(gè)質(zhì)數(shù)。
注意
fallthrough關(guān)鍵字不會(huì)檢查它下一個(gè)將會(huì)落入執(zhí)行的 case 中的匹配條件。fallthrough簡(jiǎn)單地使代碼繼續(xù)連接到下一個(gè) case 中的代碼,這和 C 語(yǔ)言標(biāo)準(zhǔn)中的switch語(yǔ)句特性是一樣的。
在 Swift 中,你可以在循環(huán)體和條件語(yǔ)句中嵌套循環(huán)體和條件語(yǔ)句來(lái)創(chuàng)造復(fù)雜的控制流結(jié)構(gòu)。并且,循環(huán)體和條件語(yǔ)句都可以使用 break 語(yǔ)句來(lái)提前結(jié)束整個(gè)代碼塊。因此,顯式地指明 break 語(yǔ)句想要終止的是哪個(gè)循環(huán)體或者條件語(yǔ)句,會(huì)很有用。類似地,如果你有許多嵌套的循環(huán)體,顯式指明 continue 語(yǔ)句想要影響哪一個(gè)循環(huán)體也會(huì)非常有用。
為了實(shí)現(xiàn)這個(gè)目的,你可以使用標(biāo)簽(statement label)來(lái)標(biāo)記一個(gè)循環(huán)體或者條件語(yǔ)句,對(duì)于一個(gè)條件語(yǔ)句,你可以使用 break 加標(biāo)簽的方式,來(lái)結(jié)束這個(gè)被標(biāo)記的語(yǔ)句。對(duì)于一個(gè)循環(huán)語(yǔ)句,你可以使用 break 或者 continue 加標(biāo)簽,來(lái)結(jié)束或者繼續(xù)這條被標(biāo)記語(yǔ)句的執(zhí)行。
聲明一個(gè)帶標(biāo)簽的語(yǔ)句是通過(guò)在該語(yǔ)句的關(guān)鍵詞的同一行前面放置一個(gè)標(biāo)簽,作為這個(gè)語(yǔ)句的前導(dǎo)關(guān)鍵字(introducor keyword),并且該標(biāo)簽后面跟隨一個(gè)冒號(hào)。下面是一個(gè)針對(duì) while 循環(huán)體的標(biāo)簽語(yǔ)法,同樣的規(guī)則適用于所有的循環(huán)體和條件語(yǔ)句。
label name: while condition {
statements
}
下面的例子是前面章節(jié)中蛇和梯子的適配版本,在此版本中,我們將使用一個(gè)帶有標(biāo)簽的 while 循環(huán)體中調(diào)用 break 和 continue 語(yǔ)句。這次,游戲增加了一條額外的規(guī)則:
如果某次擲骰子使你的移動(dòng)超出第 25 個(gè)方塊,你必須重新擲骰子,直到你擲出的骰子數(shù)剛好使你能落在第 25 個(gè)方塊中。
游戲的棋盤和之前一樣:

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
這個(gè)版本的游戲使用 while 循環(huán)和 switch 語(yǔ)句來(lái)實(shí)現(xiàn)游戲的邏輯。while 循環(huán)有一個(gè)標(biāo)簽名 gameLoop,來(lái)表明它是游戲的主循環(huán)。
該 while 循環(huán)體的條件判斷語(yǔ)句是 while square !=finalSquare,這表明你必須剛好落在方格25中。
gameLoop: while square != finalSquare {
diceRoll += 1
if diceRoll == 7 { diceRoll = 1 }
switch square + diceRoll {
case finalSquare:
// 骰子數(shù)剛好使玩家移動(dòng)到最終的方格里,游戲結(jié)束。
break gameLoop
case let newSquare where newSquare > finalSquare:
// 骰子數(shù)將會(huì)使玩家的移動(dòng)超出最后的方格,那么這種移動(dòng)是不合法的,玩家需要重新擲骰子
continue gameLoop
default:
// 合法移動(dòng),做正常的處理
square += diceRoll
square += board[square]
}
}
print("Game over!")
每次循環(huán)迭代開(kāi)始時(shí)擲骰子。與之前玩家擲完骰子就立即移動(dòng)不同,這里使用了 switch 語(yǔ)句來(lái)考慮每次移動(dòng)可能產(chǎn)生的結(jié)果,從而決定玩家本次是否能夠移動(dòng)。
break gameLoop 語(yǔ)句跳轉(zhuǎn)控制去執(zhí)行 while 循環(huán)體后的第一行代碼,意味著游戲結(jié)束。continue gameLoop 語(yǔ)句結(jié)束本次 while 循環(huán),開(kāi)始下一次循環(huán)。diceRoll 個(gè)方格,然后游戲邏輯再處理玩家當(dāng)前是否處于蛇頭或者梯子的底部。接著本次循環(huán)結(jié)束,控制跳轉(zhuǎn)到 while 循環(huán)體的條件判斷語(yǔ)句處,再?zèng)Q定是否需要繼續(xù)執(zhí)行下次循環(huán)。注意
如果上述的
break語(yǔ)句沒(méi)有使用gameLoop標(biāo)簽,那么它將會(huì)中斷switch語(yǔ)句而不是while循環(huán)。使用gameLoop標(biāo)簽清晰的表明了break想要中斷的是哪個(gè)代碼塊。同時(shí)請(qǐng)注意,當(dāng)調(diào)用
continue gameLoop去跳轉(zhuǎn)到下一次循環(huán)迭代時(shí),這里使用gameLoop標(biāo)簽并不是嚴(yán)格必須的。因?yàn)樵谶@個(gè)游戲中,只有一個(gè)循環(huán)體,所以continue語(yǔ)句會(huì)影響到哪個(gè)循環(huán)體是沒(méi)有歧義的。然而,continue語(yǔ)句使用gameLoop標(biāo)簽也是沒(méi)有危害的。這樣做符合標(biāo)簽的使用規(guī)則,同時(shí)參照旁邊的break gameLoop,能夠使游戲的邏輯更加清晰和易于理解。
像 if 語(yǔ)句一樣,guard 的執(zhí)行取決于一個(gè)表達(dá)式的布爾值。我們可以使用 guard 語(yǔ)句來(lái)要求條件必須為真時(shí),以執(zhí)行 guard 語(yǔ)句后的代碼。不同于 if 語(yǔ)句,一個(gè) guard 語(yǔ)句總是有一個(gè) else 從句,如果條件不為真則執(zhí)行 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(["name": "John"])
// 輸出 "Hello John!"
// 輸出 "I hope the weather is nice near you."
greet(["name": "Jane", "location": "Cupertino"])
// 輸出 "Hello Jane!"
// 輸出 "I hope the weather is nice in Cupertino."
如果 guard 語(yǔ)句的條件被滿足,則繼續(xù)執(zhí)行 guard 語(yǔ)句大括號(hào)后的代碼。將變量或者常量的可選綁定作為 guard 語(yǔ)句的條件,都可以保護(hù) guard 語(yǔ)句后面的代碼。
如果條件不被滿足,在 else 分支上的代碼就會(huì)被執(zhí)行。這個(gè)分支必須轉(zhuǎn)移控制以退出 guard 語(yǔ)句出現(xiàn)的代碼段。它可以用控制轉(zhuǎn)移語(yǔ)句如 return,break,continue 或者 throw 做這件事,或者調(diào)用一個(gè)不返回的方法或函數(shù),例如 fatalError()。
相比于可以實(shí)現(xiàn)同樣功能的 if 語(yǔ)句,按需使用 guard 語(yǔ)句會(huì)提升我們代碼的可讀性。它可以使你的代碼連貫的被執(zhí)行而不需要將它包在 else 塊中,它可以使你在緊鄰條件判斷的地方,處理違規(guī)的情況。
Swift 內(nèi)置支持檢查 API 可用性,這可以確保我們不會(huì)在當(dāng)前部署機(jī)器上,不小心地使用了不可用的 API。
編譯器使用 SDK 中的可用信息來(lái)驗(yàn)證我們的代碼中使用的所有 API 在項(xiàng)目指定的部署目標(biāo)上是否可用。如果我們嘗試使用一個(gè)不可用的 API,Swift 會(huì)在編譯時(shí)報(bào)錯(cuò)。
我們?cè)?if 或 guard 語(yǔ)句中使用 可用性條件(availability condition)去有條件的執(zhí)行一段代碼,來(lái)在運(yùn)行時(shí)判斷調(diào)用的 API 是否可用。編譯器使用從可用性條件語(yǔ)句中獲取的信息去驗(yàn)證,在這個(gè)代碼塊中調(diào)用的 API 是否可用。
if #available(iOS 10, macOS 10.12, *) {
// 在 iOS 使用 iOS 10 的 API, 在 macOS 使用 macOS 10.12 的 API
} else {
// 使用先前版本的 iOS 和 macOS 的 API
}
以上可用性條件指定,if 語(yǔ)句的代碼塊僅僅在 iOS 10 或 macOS 10.12 及更高版本才運(yùn)行。最后一個(gè)參數(shù),*,是必須的,用于指定在所有其它平臺(tái)中,如果版本號(hào)高于你的設(shè)備指定的最低版本,if 語(yǔ)句的代碼塊將會(huì)運(yùn)行。
在它一般的形式中,可用性條件使用了一個(gè)平臺(tái)名字和版本的列表。平臺(tái)名字可以是 iOS,macOS,watchOS 和 tvOS——請(qǐng)?jiān)L問(wèn)聲明屬性來(lái)獲取完整列表。除了指定像 iOS 8 或 macOS 10.10 的大版本號(hào),也可以指定像 iOS 11.2.6 以及 macOS 10.13.3 的小版本號(hào)。
if #available(platform name version, ..., *) {
APIs 可用,語(yǔ)句將執(zhí)行
} else {
APIs 不可用,語(yǔ)句將不執(zhí)行
}