guard 是一個要求表達式的值為 true 從而繼續(xù)執(zhí)行的條件語句。如果表達式為 false,則會執(zhí)行必須提供的 else 分支。
func sayHello(numberOfTimes: Int) -> () {
guard numberOfTimes > 0 else {
return
}
for _ in 1...numberOfTimes {
print("Hello!")
}
}
guard 語句中的 else 分支必須退出當(dāng)前的區(qū)域,通過使用 return 來退出函數(shù),continue 或者 break來退出循環(huán),或者使用像 fatalError(_:file:line:) 這種返回 Never 的函數(shù)。
使用 guard 來避免過多的縮進和錯誤
比如,我們要實現(xiàn)一個 readBedtimeStory() 函數(shù):
enum StoryError:Error {
case missing
case illegible
case tooScary
}
//未使用guard的函數(shù)
func readBedtimeStory() throws {
if let url = Bundle.main.url(forResource: "books", withExtension: "txt") {
if let data = try? Data(contentsOf: url), let story = String(data: data, encoding: .utf8) {
if story.contains("??") {
throw StoryError.tooScary
} else {
print("Once upon a time...\(story)")
}
} else {
throw StoryError.illegible
}
} else {
throw StoryError.missing
}
}
要讀一個睡前故事,我們需要能找到一本書,這本故事書必須要是可讀的,并且故事不能太嚇人(請不要讓怪物出現(xiàn)在書的結(jié)尾,謝謝你?。?。
請注意 throw 語句離檢查本身有多遠。你需要讀完整個方法來找到如果沒有 book.txt 會發(fā)生什么。
像一本好書一樣,代碼應(yīng)該講述一個故事:有著易懂的情節(jié),清晰的開端、發(fā)展和結(jié)尾。(請嘗試不要寫太多「后現(xiàn)代」風(fēng)格的代碼。)
使用 guard 語句組織代碼可以讓代碼讀起來更加的線性:
//使用guard
func readBedTimeStory2() throws {
guard let url = Bundle.main.url(forResource: "books", withExtension: "txt") else {
throw StoryError.missing
}
guard let data = try? Data(contentsOf: url), let story = String(data: data, encoding: .utf8) else {
throw StoryError.illegible
}
if story.contains("??") {
throw StoryError.tooScary
}
print("Once upon a time...\(story)")
}
這樣就好多了! 每一個錯誤都在相應(yīng)的檢查之后立刻被拋出,所以我們可以按照左手邊的代碼順序來梳理工作流的順序。
defer
defer 關(guān)鍵字為此提供了安全又簡單的處理方式:聲明一個 block,當(dāng)前代碼執(zhí)行的閉包退出時會執(zhí)行該 block。
看看下面這個包裝了系統(tǒng)調(diào)用 gethostname(2) 的函數(shù),用來返回當(dāng)前系統(tǒng)的主機名稱:
import Darwin
func currenthostName() -> String {
let capacity = Int(NI_MAXHOST)
let buffer = UnsafeMutablePointer<Int8>.allocate(capacity: capacity)
guard gethostname(buffer, capacity) == 0 else {
buffer.deallocate()
return "localhost"
}
let hostname = String(cString: buffer)
buffer.deallocate()
return hostname
}
這里有一個在最開始就創(chuàng)建的 UnsafeMutablePointer<UInt8> 用于存儲目標數(shù)據(jù),但是我既要在錯誤發(fā)生后銷毀它,又要在正常流程下不再使用它時對其進行銷毀。
這種設(shè)計很容易導(dǎo)致錯誤,而且不停地在做重復(fù)工作。
通過使用 defer 語句,我們可以排除潛在的錯誤并且簡化代碼:
func currentHostName2() -> String {
let capacity = Int(NI_MAXHOST)
let buffer = UnsafeMutablePointer<Int8>.allocate(capacity: capacity)
defer {
buffer.deallocate()
}
guard gethostname(buffer, capacity) == 0 else {
return "localhost"
}
return String(cString: buffer)
}
盡管 defer 緊接著出現(xiàn)在 allocate(capacity:) 調(diào)用之后,但它要等到當(dāng)前區(qū)域結(jié)束時才會被執(zhí)行。多虧了 defer,buffer 才能無論在哪個點退出函數(shù)都可以被釋放。
考慮在任何需要配對調(diào)用的 API 上都使用 defer,比如 allocate(capacity:) / deallocate()、wait() / signal() 和 open() / close()。
如果在 defer 語句中引用了一個變量,執(zhí)行時會用到變量最終的值。換句話說:defer 代碼塊不會捕獲變量當(dāng)前的值。
func flipFlop() -> () {
var position = "It's pronounced /haha/"
defer {
print(position)
}
position = "It's pronounced /hehe/"
defer {
print(position)
}
}
輸出:
It's pronounced /hehe/
It's pronounced /hehe/