Swift提供了多種控制流聲明。包括while
循環(huán)來多次執(zhí)行一個任務(wù);if
,guard
和switch
聲明來根據(jù)確定條件執(zhí)行不同的分支代碼;以及像break
和continue
的聲明來進(jìn)行控制轉(zhuǎn)移。
Swift同樣提供了一個for-in
循環(huán)來方便的遍歷數(shù)組,字典,區(qū)間,字符串和其它序列。
Swift的Switch
聲明相比一些類C語言的同伴要強(qiáng)大的多。case
可以匹配許多不同的模式,包括間隔匹配,元組和轉(zhuǎn)換到一個指定類型。Switch
case中匹配的值可以轉(zhuǎn)換為臨時的常量或者變量來在case體內(nèi)使用,并且可以使用where
字句在每個case中表達(dá)復(fù)雜的匹配條件。
目錄
- For-In循環(huán)
- While循環(huán)
- 條件聲明
- 控制轉(zhuǎn)移聲明
- 提前退出
- 檢查API可用性
For-In循環(huán)
使用for-in
循環(huán)遍歷一個序列,例如數(shù)組的元素,值的范圍或者字符串的字符。
這個例子使用for-in
循環(huán)遍歷一個數(shù)組的元素:
let names = ["Anna", "Alex", "Brian", "Jack"]
for name in names {
print("Hello, \(name)!")
}
// Hello, Anna!
// Hello, Alex!
// Hello, Brian!
// Hello, Jack!
同樣可以遍歷字典來訪問它的鍵值對。遍歷字典時它的每個元素都被當(dāng)做(key, value)
類型的元組返回,可以將這個元組的成員分解成有明確名字的常量以便在for-in
循環(huán)體內(nèi)使用。下例中,字典的鍵被分解為名為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
字典的元素是無序的,遍歷這些元素時不能保證它們被訪問的順序。特別的,向字典插入元素的順序并沒有定義它們被遍歷的順序。更多數(shù)組和字典的內(nèi)容,詳見Swift-集合類型。
for-in
循環(huán)同樣適用于數(shù)值范圍。這個例子打印了一個五倍表的前幾個條目:
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
(包括5
,因?yàn)槭褂昧碎]區(qū)間運(yùn)算符...
)的數(shù)值區(qū)間。index
的值被設(shè)置成區(qū)間的第一個值,然后執(zhí)行循環(huán)體內(nèi)的聲明。本例中,這個循環(huán)只包含一個聲明,用來打印index
當(dāng)前值在五倍表的條目。當(dāng)執(zhí)行完聲明后,index
的值更新為區(qū)間的第二個值,函數(shù)print(_:separator:terminator:)
再次被調(diào)用。這個過程持續(xù)到區(qū)間的末尾。
上例中,index
是一個常量,它的值在循環(huán)每次遍歷開始時被自動設(shè)置。因此index
在使用之前無需聲明。它通過被包含在循環(huán)聲明中而被隱式的聲明,無需let
聲明關(guān)鍵字。
如果不需要一個序列的每個值,可以通過使用下劃線代替變量名來忽略這個值。
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"
上例計算了某個數(shù)的n次冪(例子中是3的10次冪)。它使用一個從1
到10
的閉區(qū)間將基數(shù)1
連乘10次3
。對于這個計算,循環(huán)每次的計數(shù)值是沒有必要的,代碼本身就執(zhí)行了正確的次數(shù)。使用下劃線代替循環(huán)變量會忽略計數(shù)值并且不能訪問每次循環(huán)的當(dāng)前值。
在某些情況下,你不想使用包含邊界的閉區(qū)間。假設(shè)想在表盤上每隔一分鐘繪制一條刻度線。你想從0
分鐘開始繪制60
條刻度線。使用半開區(qū)間運(yùn)算符(..<
)來包括更低的邊界但是不包括更高的邊界。
let minutes = 60
for tickMark in 0..<minutes {
// render the tick mark each minute (60 times)
}
一些用戶在界面上想要更少的刻度線。他們更喜歡每隔5
秒繪制一條刻度線。使用stride(from:to:by:)
函數(shù)跳過不想要的刻度。
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)
}
閉區(qū)間同樣適用,但要使用stride(from:through:by:)
函數(shù):
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循環(huán)
While
循環(huán)反復(fù)執(zhí)行一段語句直到一個條件為false
。這種循環(huán)最佳的使用場景是在第一次循環(huán)開始前遍歷次數(shù)未知的情況。Swift提供了兩種While
循環(huán):
-
While
每次循環(huán)開始前會評估條件 -
repeat-while
每次循環(huán)結(jié)束后會評估條件
While
While
循環(huán)先評估一個單獨(dú)的條件再開始。如果條件為true
,反復(fù)執(zhí)行一組語句直到條件為false
。
下面是一個While
循環(huán)的通常格式:
while condition {
statements
}
這個例子玩了一個簡單的蛇和梯子 的游戲:
這個游戲的規(guī)則如下:
- 這個板子有25個方塊,目標(biāo)是到達(dá)或者超過第25個方塊
- 玩家從左下角的“方塊0”開始
- 每個回合,你搖一個六邊的的骰子來或者去前進(jìn)的步數(shù),前進(jìn)路線如右圖的點(diǎn)狀箭頭所示
- 如果你走到了梯子的底部,爬到梯子的頂部
- 如果到了蛇的頭部,爬到蛇的尾部
這個游戲板子表示為一個Int
值的數(shù)組。這個數(shù)組的大小取決于一個名為finalSquare
的常量,這個常量用來初始化這個數(shù)組并檢查勝利的條件。以為玩家在板子外部開始,也就是“方塊0”,因此板子初始化為包含26個0而不是25.
let finalSquare = 25
var board = [Int](repeating: 0, count: finalSquare + 1)
然后一些方塊為蛇和梯子設(shè)置了指定的值。含梯底的方塊有一個正值幫助你向前移動,然而含蛇頭的梯子有一個負(fù)值讓你向后后退。
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
的差值)。為了對齊值和聲明,一元加運(yùn)算符(+
)顯示的與一元減運(yùn)算符(-
)配合使用,并且低于10
的值加0補(bǔ)齊。(雖然格式上的技巧不是嚴(yán)格必要的,但是它們可以保證整潔的代碼)
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
的值不是一個隨機(jī)值,它的起始值是0
。每次while
循環(huán),diceRoll
的值加1并檢查值是否過大。一旦diceRoll
的值等于7
,骰子值就變得過大因此重置為1
。結(jié)果diceRoll
的值總是1
,2
,3
,4
,5
,6
,1
,2
循環(huán)。
擲骰子后,玩家前進(jìn)diceRoll
步。有可能玩家超出了方塊25,這時游戲結(jié)束。為了處理這種情況,代碼會檢查square
小于board
數(shù)組的count
屬性。如果square
是有效的,存儲在board[square]
的值會加到square
讓玩家爬上梯子或者滑到蛇尾。
注意
如果不進(jìn)行檢查,board[square]
也許會試圖獲取超出board
數(shù)組邊界的值,這會導(dǎo)致運(yùn)行時錯誤。
當(dāng)前的while
循環(huán)然后結(jié)束,然后檢查循環(huán)條件來確定是否繼續(xù)執(zhí)行循環(huán)。如果玩家已經(jīng)到達(dá)或者越過方塊25
,那么循環(huán)條件為false
游戲結(jié)束。
這個例子使用while
循環(huán)是合適的,因?yàn)楫?dāng)while
循環(huán)開始時游戲的長度是不確定的。相反,直到滿足一個特殊條件循環(huán)才會終止。
Repeat-While
另一個while
循環(huán)的變種是repeat-while
循環(huán),它在考慮循環(huán)條件之前先執(zhí)行一遍循環(huán)代碼。然后繼續(xù)循環(huán)直到條件為假。
注意
Swift的repeat-while
循環(huán)和其它語言的do-while
是類似的。
以下是repeat-while
循環(huán)的通用格式:
repeat {
statements
} while condition
下面的例子使用repeat-while
循環(huán)重寫的蛇和梯子的游戲。finalSquare
,board
,square
和diceRoll
值的初始化是和while
循環(huán)一樣。
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
這個版本的游戲中,循環(huán)中第一步檢查梯子或者蛇。板子中沒有梯子可以讓玩家直接到達(dá)方塊25,因此不可能通過爬上一個梯子還贏得游戲。因此循環(huán)的第一步檢查蛇或者梯子是安全的。
在游戲的開始,玩家站在“方塊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!")
當(dāng)代碼檢查了蛇和梯子后,開始擲骰子,玩家前進(jìn)diceRoll
步。當(dāng)前循環(huán)結(jié)束。循環(huán)條件(while square < finalSquare
)和之前一樣,但是這次直到第一次循環(huán)完畢才評估。在這個例子中repeat-while
循環(huán)結(jié)構(gòu)要比while
循環(huán)結(jié)構(gòu)更合適。在repeat-while
循環(huán)中,當(dāng)循環(huán)的while
條件確認(rèn)square
仍在板子上后,square += board[square]
總是立刻執(zhí)行。這種方式避免了之前版本檢查數(shù)組邊界的需求。
條件聲明
通常需要根據(jù)指定的條件執(zhí)行不同的代碼片段。當(dāng)一個錯誤發(fā)生時,你也許需要一段額外的代碼,或者當(dāng)一個值過大或者過小時展示一條信息。想做到這樣,可以使你的部分代碼是有條件的。
Swift提供了兩種方式添加條件分支:if
聲明和switch
聲明。通常,使用if
聲明來評估只包含幾個可能輸出的簡單情況。switch
聲明更適用于包含多個可能排列的更加復(fù)雜的情況,并且在需要模式匹配來幫助選擇合適的代碼分支執(zhí)行的情況下更加有用。
If
if
聲明最簡單的形式只有一個if
條件。只有當(dāng)條件為真時才執(zhí)行相關(guān)代碼。
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華氏度。如果是真,打印一條消息,否則不打印消息繼續(xù)執(zhí)行if
聲明終止括號后面的語句。
if
聲明可以提供可供選擇的語句集合,叫做else從句。用在if
條件為false
時。這些語句由else
關(guān)鍵字指示。
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."
兩個分支總有一個被執(zhí)行。因?yàn)闇囟壬仙搅?code>40華氏度,不算太冷,不再需要建議圍圍巾,因此else
分支被觸發(fā)。
可以將多個if
語句鏈接在一起來實(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.")
}
// Prints "It's really warm. Don't forget to wear sunscreen."
這里,一個額外的if
語句用來處理特別熱的溫度。最后的else
語句保留了下來,用于打印既不冷也不熱時的消息。
實(shí)際上最后的else
語句是可選的,當(dāng)不需要完整判斷情況的時候可以排除。
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.")
}
因?yàn)闇囟燃炔焕湟膊粺?,所以不會觸發(fā)if
或else if
分支,也就不會打印任何消息。
Switch
switch
語句會嘗試把某個值與若干個模式進(jìn)行匹配。根據(jù)第一個匹配成功的模式,switch語句會執(zhí)行對應(yīng)的代碼。當(dāng)處理多個可能得狀態(tài)時,通常用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構(gòu)成,每個由case
關(guān)鍵字開始。為了匹配某些更特定的值,Swift 提供了幾種方法來進(jìn)行更復(fù)雜的模式匹配,這些模式將在本節(jié)的稍后部分提到。
與if
語句的語句體類似,每一個case
都是代碼執(zhí)行的一條分支。switch
語句會決定哪一條分支應(yīng)該被執(zhí)行,這個流程被稱作根據(jù)給定的值切換。
每一個switch
語句必須是窮舉的。這就是說,每一個可能的值都必須有一個case
分支與之對應(yīng)。在某些不可能涵蓋所有值的情況下,你可以使用默認(rèn)分支來涵蓋其它所有沒有對應(yīng)的值,這個默認(rèn)分支由default
關(guān)鍵字表示,且必須在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
。 因?yàn)?code>switch語句必須有一個case
分支用于覆蓋所有可能的字符,而不僅僅是所有的英文字母,所以switch
語句使用一個default
分支來匹配除了a
和z
外的所有值,這個分支保證了swith
語句的窮舉的。
不存在隱式的貫穿
與C和OC中的switch
語句不同,在 Swift 中,它不會默認(rèn)的自動貫穿每個分支的底部進(jìn)入下一個分支。相反,當(dāng)?shù)谝粋€匹配的case
執(zhí)行完畢后,整個switch
語句會終止,不需顯式地使用break
語句。這使得switch
語句相比于C語言更安全、更易用,也避免了錯誤的執(zhí)行多個case
分支。
注意
雖然在Swift中break
不是必須的,但你依然可以使用break
語句來匹配或忽略一個特殊的分支或者在一個匹配的分支的代碼執(zhí)行完畢前提前跳出。
每一個case
分支都必須包含至少一條語句。像下面這樣書寫代碼是無效的,因?yàn)榈谝粋€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":
不包含任何可執(zhí)行語句。這就避免了意外地從一個case
分支貫穿到另外一個,確保了意圖更明確的安全的代碼。
為了讓單個case
同時匹配“a”
和“A”
,可以將這個兩個值組合成一個復(fù)合匹配,并且用逗號分開:
let anotherCharacter: Character = "a"
switch anotherCharacter {
case "a", "A":
print("The letter A")
default:
print("Not the letter A")
}
// Prints "The letter A"
為了可讀性,一個復(fù)合匹配可以寫成多行
注意
如果想要顯式貫穿case
分支,請使用fallthrough
關(guān)鍵字。
區(qū)間匹配
可以用一個區(qū)間來檢查switch
分支的值的包含情況。下面的例子使用數(shù)值區(qū)間匹配來輸出任意數(shù)字對應(yīng)的自然語言格式:
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
都將其與一個值或區(qū)間進(jìn)行比較。因?yàn)?code>approximateCount在12到100之間,所以naturalCount
被賦值"dozens of"
,執(zhí)行跳出switch
語句。
元組
我們可以使用元組在同一個switch
語句中測試多個值。元組中的每個元素可以使用值或者區(qū)間來測試。另外,使用下劃線(_),也就是通配符來匹配所有可能的值。
下面的例子展示了如何使用一個(Int, Int)
類型的元組來分類下圖中的點(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")
}
// Prints "(1, 1) is inside the box"
switch
語句會判斷這個點(diǎn)是否在原點(diǎn)(0, 0),是否在紅色的x軸上,是否在橘黃色的y軸上,是否在一個以原點(diǎn)為中心的4x4的藍(lán)色矩形里,或者在這個矩形外面。不像C語言,Swift 允許多個
case
匹配同一個值。實(shí)際上,在這個例子中,點(diǎn)(0, 0)可以匹配所有四個case。但是,如果存在多個匹配,那么只會執(zhí)行第一個被匹配到的 case 分支??紤]點(diǎn)(0, 0)會首先匹配case (0, 0),因此剩下的能夠匹配的分支都會被忽視掉。
值綁定
一個switch
分支允許將匹配的值綁定到臨時的常量或變量,以便在分支體內(nèi)使用。這種行為被稱為值綁定,因?yàn)槠ヅ涞闹翟赾ase分支體內(nèi),與臨時的常量或變量綁定。
下面的例子展示了如何在一個(Int, Int)
類型的元組中使用值綁定來分類下圖中的點(diǎn)(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"

`switch`語句會判斷這個點(diǎn)是否在紅色的x軸上,是否在橘黃色的y軸上,或者不在坐標(biāo)軸上。
這三個`switch`分支都聲明了常量`x`和`y`的占位符,用于臨時獲取元組`anotherPoint`的一個或兩個值。第一個分支`case (let x, 0)`將匹配一個`y`為`0`的點(diǎn),并把這個點(diǎn)的橫坐標(biāo)賦給臨時的常量`x`。類似的,第二個`case (0, let y)`將匹配一個`x`為`0`的點(diǎn),并把這個點(diǎn)的縱坐標(biāo)賦給臨時的常量`y`。
一旦聲明了這些臨時的常量,它們就可以在其對應(yīng)的c分支里使用。在這個例子中,它們用于打印給定點(diǎn)的位置。
這個`switch`語句不包含`default`分支。最后一個分支`case let(x, y)`聲明了一個可以匹配任何值的元組。因?yàn)閌anotherPoint`總是一個包含兩個值的元組,這個分支滿足所有可能剩余的值,因此無需一個`default`分支來窮舉`switch`語句。
###Where
一個`switch`分支可以使用`where`語句來判斷額外的條件。
下面的例子把下圖中的點(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")
}
// Prints "(1, -1) is on the line x == -y"
switch
語句會判斷這個點(diǎn)是否在綠色的對角線x == y
上,是否在紫色的對角線x == -y
上,或者不在對角線上。這三個
switch
分支都聲明了常量x
和y
的占位符,用于臨時獲取元組yetAnotherPoint
的兩個值。這兩個常量被用作where
語句的一部分,來創(chuàng)建一個動態(tài)的過濾器。僅當(dāng)where
語句的條件為true
時,匹配到的 case 分支才會被執(zhí)行。就像是值綁定中的例子,這個分支滿足所有可能剩余的值,因此無需一個
default
分支來窮舉switch
語句。
復(fù)合匹配
當(dāng)多個條件可以使用同一種方法來處理時,可以將這幾種可能放在同一個case后面,并且用逗號隔開。當(dāng)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
分支匹配了其它所有字符。
復(fù)合匹配同樣可以包含值綁定。復(fù)合匹配里所有的匹配模式,都必須包含相同的值綁定。并且每一個綁定都必須獲取到相同類型的值。這保證了,無論復(fù)合匹配中的哪個模式發(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")
}
// Prints "On an axis, 9 from the origin"
上面的case
有兩個模式:(let distance, 0)
匹配了在x軸上的值,(0, let distance)
匹配了在y軸上的值。兩個模式都綁定了distance
,并且distance
在兩種模式下都是整型——這意味著分支體內(nèi)的代碼總是能獲取到distance
的值。
控制轉(zhuǎn)移語句
控制轉(zhuǎn)移語句通過從一片代碼轉(zhuǎn)移控制到另一片代碼來改變你代碼的執(zhí)行順序。Swift有五種控制轉(zhuǎn)移語句:
continue
break
fallthrough
return
throw
continue
、break
和fallthrough
語句會在以下介紹。return
在函數(shù)介紹,throw
在錯誤拋出介紹。
Continue
continue
語句通知一個循環(huán)停止本次循環(huán),重新開始下次循環(huán)。就好像在說“本次循環(huán)我已經(jīng)執(zhí)行完了”,但是并不會離開整個循環(huán)體。
下面的例子把一個小寫字符串中的元音字母和空格字符移除,生成了一個含義模糊的短句:
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"
在上面的代碼中,只要匹配到元音字母或者空格字符,就調(diào)用continue
語句使本次循環(huán)結(jié)束,重新開始下次循環(huán)。
Break
break
語句會立刻結(jié)束整個控制流的執(zhí)行。當(dāng)你想要更早的結(jié)束一個switch
代碼塊或者一個循環(huán)體時,你都可以使用break
語句。
循環(huán)語句中的Break
當(dāng)在一個循環(huán)體中使用break
時,會立刻中斷該循環(huán)體的執(zhí)行,然后跳轉(zhuǎn)到表示循環(huán)體結(jié)束的大括號(}
)后的代碼。不會再有本次循環(huán)的代碼被執(zhí)行,也不會再有下次的循環(huán)產(chǎn)生。
Switch語句中的Break
當(dāng)在一個switch
代碼塊中使用break
時,會立即中斷該switch
代碼塊的執(zhí)行,并且跳轉(zhuǎn)到表示switch
代碼塊結(jié)束的大括號(}
)后的第一行代碼。
這種特性可以被用來匹配或者忽略一個或多個分支。因?yàn)镾wift的 switch
需要包含所有的分支而且不允許有為空的分支,有時為了使你的意圖更明顯,需要特意匹配或者忽略某個分支。那么當(dāng)你想忽略某個分支時,可以在該分支內(nèi)寫上break
語句。當(dāng)那個分支被匹配到時,分支內(nèi)的break
語句立即結(jié)束switch
代碼塊。
注意
當(dāng)一個switch
分支僅僅包含注釋時,會被報編譯時錯誤。注釋不是代碼語句而且也不能讓switch
分支達(dá)到被忽略的效果。你應(yīng)該使用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
是否是拉丁,阿拉伯,中文或者泰語中的1
到4
之一。如果被匹配到,該switch
分支語句給Int?
類型變量possibleIntegerValue
設(shè)置一個整數(shù)值。
當(dāng)switch
代碼塊執(zhí)行完后,使用可選綁定來判斷possibleIntegerValue
是否曾經(jīng)被設(shè)置過值。因?yàn)槭强蛇x類型的緣故,possibleIntegerValue
有一個隱式的初始值nil
,所以僅僅當(dāng)possibleIntegerValue
曾被switch代碼塊的前四個分支中的某個設(shè)置過一個值時,可選的綁定才會被判定為成功。
在上面的例子中,想要把Character
所有的的可能性都枚舉出來是不現(xiàn)實(shí)的,所以使用default
分支來包含所有上面沒有匹配到字符的情況。由于這個default
分支不需要執(zhí)行任何動作,所以它只寫了一條break
語句。一旦落入到default
分支中后,break
語句就完成了該分支的所有代碼操作,代碼繼續(xù)向下,開始執(zhí)行if let
語句。
Fallthrough
Swift中的switch
不會從上一個case分支落入到下一個case分支中。相反,只要第一個匹配到的case分支完成了它需要執(zhí)行的語句,整個switch
代碼塊完成了它的執(zhí)行。相比之下,C 語言要求你顯式地插入break
語句到每個case分支的末尾來阻止自動落入到下一個case分支中。Swift的這種避免默認(rèn)落入到下一個分支中的特性意味著它的switch
功能要比 C 語言的更加清晰和可預(yù)測,可以避免無意識地執(zhí)行多個case分支從而引發(fā)的錯誤。
如果你確實(shí)需要 C 風(fēng)格的貫穿的特性,你可以在每個需要該特性的 case 分支中使用fallthrough
關(guān)鍵字。下面的例子使用fallthrough
來創(chuàng)建一個數(shù)字的描述語句。
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
并且給它設(shè)置了一個初始值。函數(shù)使用switch
邏輯來判斷integerToDescribe
變量的值。當(dāng)integerToDescribe
的值屬于列表中的質(zhì)數(shù)之一時,該函數(shù)在description
后添加一段文字,來表明這個數(shù)字是一個質(zhì)數(shù)。然后它使用fallthrough
關(guān)鍵字來“貫穿”到default
分支中。default
分支在description
的最后添加一段額外的文字,至此switch
代碼塊執(zhí)行完了。
如果integerToDescribe
的值不屬于列表中的任何質(zhì)數(shù),那么它不會匹配到第一個switch
分支。而這里沒有其他特別的分支情況,所以integerToDescribe
匹配到default
分支中。
當(dāng)switch
代碼塊執(zhí)行完后,使用print(_:separator:terminator:)
函數(shù)打印該數(shù)字的描述。在這個例子中,數(shù)字5
被準(zhǔn)確的識別為了一個質(zhì)數(shù)。
注意
fallthrough
關(guān)鍵字不會檢查它下一個將會落入執(zhí)行的 case 中的匹配條件。fallthrough
簡單地使代碼繼續(xù)連接到下一個 case 中的代碼,這和 C 語言標(biāo)準(zhǔn)中的switch語句特性是一樣的。
帶標(biāo)簽的語句
在 Swift 中,你可以在循環(huán)體和條件語句中嵌套循環(huán)體和條件語句來創(chuàng)造復(fù)雜的控制流結(jié)構(gòu)。并且,循環(huán)體和條件語句都可以使用break
語句來提前結(jié)束整個代碼塊。因此,顯式地指明break
語句想要終止的是哪個循環(huán)體或者條件語句,會很有用。類似地,如果你有許多嵌套的循環(huán)體,顯式指明continue
語句想要影響哪一個循環(huán)體也會非常有用。
為了實(shí)現(xiàn)這個目的,你可以使用標(biāo)簽來標(biāo)記一個循環(huán)體或者條件語句,對于一個條件語句,你可以使用break
加標(biāo)簽的方式,來結(jié)束這個被標(biāo)記的語句。對于一個循環(huán)語句,你可以使用break
或者continue
加標(biāo)簽,來結(jié)束或者繼續(xù)這條被標(biāo)記語句的執(zhí)行。
聲明一個帶標(biāo)簽的語句是通過在該語句的關(guān)鍵詞的同一行前面放置一個標(biāo)簽,作為這個語句的前導(dǎo)關(guān)鍵字,并且該標(biāo)簽后面跟隨一個冒號。下面是一個針對while
循環(huán)體的標(biāo)簽語法,同樣的規(guī)則適用于所有的循環(huán)體和條件語句。
label name: while condition {
statements
}
下面的例子是前面章節(jié)中蛇和梯子的適配版本,在此版本中,我們將使用一個帶有標(biāo)簽的while
循環(huán)體中調(diào)用break
和continue
語句。這次,游戲增加了一條額外的規(guī)則:
- 為了獲勝,你必須剛好落在第 25 個方塊中。
如果某次擲骰子使你的移動超出第 25 個方塊,你必須重新擲骰子,直到你擲出的骰子數(shù)剛好使你能落在第 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
循環(huán)和switch
語句來實(shí)現(xiàn)游戲的邏輯。while
循環(huán)有一個標(biāo)簽名gameLoop
,來表明它是游戲的主循環(huán)。
該while
循環(huán)體的條件判斷語句是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!")
每次循環(huán)迭代開始時擲骰子。與之前玩家擲完骰子就立即移動不同,這里使用了switch
語句來考慮每次移動可能產(chǎn)生的結(jié)果,從而決定玩家本次是否能夠移動。
- 如果骰子數(shù)剛好使玩家移動到最終的方格里,游戲結(jié)束。
break gameLoop
語句跳轉(zhuǎn)控制去執(zhí)行while
循環(huán)體后的第一行代碼,意味著游戲結(jié)束。 - 如果骰子數(shù)將會使玩家的移動超出最后的方格,那么這種移動是不合法的,玩家需要重新擲骰子。
continue gameLoop
語句結(jié)束本次while
循環(huán),開始下一次循環(huán)。 - 在剩余的所有情況中,骰子數(shù)產(chǎn)生的都是合法的移動。玩家向前移動
diceRoll
個方格,然后游戲邏輯再處理玩家當(dāng)前是否處于蛇頭或者梯子的底部。接著本次循環(huán)結(jié)束,控制跳轉(zhuǎn)到while
循環(huán)體的條件判斷語句處,再決定是否需要繼續(xù)執(zhí)行下次循環(huán)。
注意
如果上述的break
語句沒有使用gameLoop
標(biāo)簽,那么它將會中斷switch
語句而不是while
循環(huán)。使用gameLoop
標(biāo)簽清晰的表明了break
想要中斷的是哪個代碼塊。 同時請注意,當(dāng)調(diào)用continue gameLoop
去跳轉(zhuǎn)到下一次循環(huán)迭代時,這里使用gameLoop
標(biāo)簽并不是嚴(yán)格必須的。因?yàn)樵谶@個游戲中,只有一個循環(huán)體,所以continue
語句會影響到哪個循環(huán)體是沒有歧義的。然而,continue
語句使用gameLoop
標(biāo)簽也是沒有危害的。這樣做符合標(biāo)簽的使用規(guī)則,同時參照旁邊的break gameLoop
,能夠使游戲的邏輯更加清晰和易于理解。
提前退出
像if
語句一樣,guard
的執(zhí)行取決于一個表達(dá)式的布爾值。我們可以使用guard
語句來要求條件必須為真時,以執(zhí)行guard
語句后的代碼。不同于if
語句,一個guard
語句總是有一個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(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
語句的條件被滿足,則繼續(xù)執(zhí)行guard
語句大括號后的代碼。將變量或者常量的可選綁定作為guard
語句的條件,都可以保護(hù)guard
語句后面的代碼。
如果條件不被滿足,在else
分支上的代碼就會被執(zhí)行。這個分支必須轉(zhuǎn)移控制以退出guard
語句出現(xiàn)的代碼段。它可以用控制轉(zhuǎn)移語句如return
,break
,continue
或者throw
做這件事,或者調(diào)用一個不返回的方法或函數(shù),例如fatalError(_:file:line:)
。
相比于可以實(shí)現(xiàn)同樣功能的if
語句,按需使用guard
語句會提升我們代碼的可讀性。它可以使你的代碼連貫的被執(zhí)行而不需要將它包在else
塊中,它可以使你在緊鄰條件判斷的地方,處理違規(guī)的情況。
檢測 API 可用性
Swift內(nèi)置支持檢查 API 可用性,這可以確保我們不會在當(dāng)前部署機(jī)器上,不小心地使用了不可用的API。
編譯器使用 SDK 中的可用信息來驗(yàn)證我們的代碼中使用的所有 API 在項(xiàng)目指定的部署目標(biāo)上是否可用。如果我們嘗試使用一個不可用的 API,Swift 會在編譯時報錯。
我們在if
或guard
語句中使用可用性條件去有條件的執(zhí)行一段代碼,來在運(yùn)行時判斷調(diào)用的API是否可用。編譯器使用從可用性條件語句中獲取的信息去驗(yàn)證,在這個代碼塊中調(diào)用的 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 及更高的系統(tǒng)下運(yùn)行;在 macOS中,僅在 macOS 10.12 及更高才會運(yùn)行。最后一個參數(shù),*
,是必須的,用于指定在所有其它平臺中,如果版本號高于你的設(shè)備指定的最低版本,if
語句的代碼塊將會運(yùn)行。
在它一般的形式中,可用性條件使用了一個平臺名字和版本的列表。平臺名字可以是iOS
,macOS
,watchOS
和tvOS
——請?jiān)L問聲明屬性來獲取完整列表。除了指定像 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-集合類型