Swift-控制流(Control Flow)

優質神經病發表於2018-07-18

swift裡面的控制流分為For-In 迴圈While 迴圈條件語句控制轉移語句提前退出檢測 API 可用性


For-in迴圈

相對於OC,Swift對for-in做出了加強,不僅僅能對陣列遍歷,還能對字串和字典遍歷,範圍可以使用閉區間,item值可以用(_)進行忽略,還加入了stride(from:to:by:)stride(from:through:by:) 等新API

遍歷字串

let name = "Logan"
for char in name {
    print(char)
}
//L
//o
//g
//a
//n
複製程式碼

遍歷陣列

let names = ["zhangsan", "lisi", "wangwu"]
  for name in names {   
    print(name)
  }
// zhangsan 
// lisi
// wangwu
複製程式碼

遍歷字典

  • 通過遍歷字典來訪問它的鍵值對。遍歷時,字典的每項元素會已(key, value)元祖的形式返回
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
複製程式碼

區間遍歷

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
複製程式碼

忽略變數遍歷

  • 當不需要區間序列內的每一項值,可以用下劃線(_)替代變數名來忽略這個值,如下:
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"
複製程式碼

跨越遍歷

  • 當只需要取被遍歷集合中的部分資料是可以使用跨越遍歷(自己取的名字- -),方法名為:stride(from:to:by:)stride(from:through:by:) 如下:
let minutes = 60
let minuteInterval = 5
for tickMark in stride(from: 0, to: minutes, by: minuteInterval) {
    // 每5分鐘渲染一個刻度線(0, 5, 10, 15 ... 45, 50, 55)
}
複製程式碼
let hours = 12
let hourInterval = 3
for tickMark in stride(from: 3, through: hours, by: hourInterval) {
    // 每3小時渲染一個刻度線(3, 6, 9, 12)
}
複製程式碼

可以這麼理解: to不包含最後一個值,through包含最後一個值

While迴圈

while迴圈類似OC的用法,一直執行到條件變成false。而swift提供兩種迴圈形式:whilerepeat-while(每次在迴圈結束後檢查條件是否符合,類似OC的do-while

while

  • 每次迴圈開始前檢查條件是否符合(先判斷再迴圈)如下:
var condition = 0
while condition < 3 {
    condition += 1
    print(condition)

}
// 1
// 2
// 3
複製程式碼

repeat-while

  • 每次在迴圈結束後檢查條件是否符合(先迴圈然後再判斷,類似OC的do-while)如下:
var condition = 3
repeat {
    condition += 1
    print(condition)
} while condition < 3
// 4
複製程式碼

條件語句

swift提供兩種條件語句ifswitch

if條件語句

  • 先判斷if後面的語句是否為true,然後執行相關程式碼。if 語句允許二選一執行,叫做 else 從句。也就是當條件為 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.")
}
// 輸出 "It's not that cold. Wear a t-shirt."
複製程式碼

switch條件語句

  • switch語句會嘗試把某個值與若干個模式進行匹配。根據第一個匹配成功的模式,switch語句會執行對應的程式碼。當有可能的情況較多時,通常switch語句替換if語句。每一個case都是程式碼執行的一條分支。switch語句會決定哪一條分支應該被執行。這個流程被稱作根據給定的值切換(switching),switch語句必須完備,每一個可能的值都必須至少有一個case分支與之對應。當某些不能涵蓋所有值得情況下,可以使用預設default分支來涵蓋其它所有沒有對應的值如下:
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"
複製程式碼

不存在隱式的貫穿

  • 與C和OC中的 switch 語句不同,在swift中,當匹配的case分支中的程式碼執行完畢後,程式會終止switch語句,而不會繼續執行下一個case分支。也就是說,不需要在case分支中使用break。所以分支下必須要有執行語句,不然會編譯錯誤如下:
let anotherCharacter: Character = "a"
switch anotherCharacter {
case "a": // 無效,這個分支下面沒有語句
case "A":
    print("The letter A")
default:
    print("Not the letter A")
}
// 這段程式碼會報編譯錯誤
複製程式碼

為了讓單個 case 同時匹配 aA,可以將這個兩個值組合成一個複合匹配,並且用逗號分開如下:

let anotherCharacter: Character = "a"
switch anotherCharacter {
case "a", "A":
    print("The letter A")
default:
    print("Not the letter A")
}
// 輸出 "The letter A
複製程式碼

switch區間分配

  • case分支的模式也可以是一個值的區間,使用區間匹配來輸出任意數字對應的自然語言格式如下:
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."
複製程式碼

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")
}
// 輸出 "(1, 1) is inside the box"
複製程式碼

注: 不像OC,swift允許多個case匹配同一個值。實際上,如果 somePoint = (0, 0),則可以匹配上述的四個case。但是存在多個匹配,那麼只會執行第一個被匹配到的case分支。考慮 *(0,0)*會首先匹配case(0,0),因而身下的能夠匹配的分支都會被忽視掉。

繫結值

  • case分支允許將分配的值宣告為臨時常量或變數,並且在case分支內使用。這種行為被稱為 繫結值,因為匹配的值在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))")
}
// 輸出 "on the x-axis with an x value of 2"
複製程式碼

注: 繫結值只作用於switch對應的case內。上面例子中的三個case都宣告瞭常量xy的佔位符,用於臨時獲取元祖anotherPoint的一個或兩個值。第一個case *case(let x,0)*將匹配一個縱座標為0的點,並吧這個點的橫座標賦值給臨時常量x。類似的第二個case *case(0,let y)*將匹配一個橫座標為0的點,並把這個點的縱座標賦值給臨時常量y

Where

  • case分支的模式可以使用where語句來判斷額外的條件如下:
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"
複製程式碼

注: 上述程式碼中的where實質相當於case分支中的額外判定條件生產的一個過濾器,當where語句的條件為true時,匹配到的case分支才會被執行。

控制轉移語句

Swift中的控制轉移語句共有下面五種: continuebreakfallthroughreturnthrow

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)
// 輸出 "grtmndsthnklk"
複製程式碼

Break

  • break語句會立刻結束整個控制流的執行,然後跳轉到表示迴圈體結束的大括號後的第一行程式碼,不會再有本次迴圈的程式碼被執行,也不會有下次的迴圈產生。在switch中基本不用,可以在if迴圈語句中來提前結束,避免過多的遍歷次數,舉例如下:
let students = [["name":"xiaoming", "height":155], ["name":"xiaohua", "height":160], ["name":"xiaoli", "height":172]]
for student in students {
    if student["height"] as! Int >= 160 {
        print("There have students who are taller than 160")
        break
    }
}
// There have students who are taller than 160  *(只執行一次)
複製程式碼

fallthrough貫穿

  • 在Swift裡,switch語句不會從上一個分支跳轉到下一個分支中,只要匹配到相應的分支,就會去完成分支下的執行語句,整個switch程式碼塊就會完成。加入fallthrough後就會繼續執行下個case分支,舉例如下:
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."
複製程式碼

注:fallthrough關鍵字不會檢查下一個執行條件的case中的匹配條件。而是簡單粗暴的繼續連線到下個case分支的執行程式碼中。

提前退出

  • if語句一樣,guard的執行取決於一個表示式的布林值。我們可以使用guard語句來要求條件必須為真時,以執行guard程式碼塊後的程式碼。不同於if語句,一個guard語句只有一個else從句,如果條件不為真則執行else從句中的程式碼。可以理解為guard實際是省略了if條件下執行程式碼塊而只有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."
複製程式碼

注:else這個分支必須轉移控制以退出 guard 語句出現的程式碼段。它可以用控制轉移語句如 return,break,continue 或者 throw 做這件事,或者呼叫一個不返回的方法或函式,例如 fatalError()

檢測 API 可用性

  • Swift 內建支援檢查 API 可用性,這可以確保我們不會在當前部署機器上,不小心地使用了不可用的 API。 編譯器使用 SDK 中的可用資訊來驗證我們的程式碼中使用的所有 API 在專案指定的部署目標上是否可用。如果我們嘗試使用一個不可用的 API,Swift 會在編譯時報錯。 我們在 ifguard 語句中使用 可用性條件(availability condition)去有條件的執行一段程式碼,來在執行時判斷呼叫的 API 是否可用。編譯器使用從可用性條件語句中獲取的資訊去驗證,在這個程式碼塊中呼叫的 API 是否可用。舉例如下:
if #available(iOS 10, macOS 10.12, *) {
    // 在 iOS 使用 iOS 10 的 API, 在 macOS 使用 macOS 10.12 的 API
} else {
    // 使用先前版本的 iOS 和 macOS 的 API
}
複製程式碼

相關文章