guard 是一个要求表达式的值为 true 从而继续执行的条件语句。如果表达式为 false,则会执行必须提供的 else 分支。
func sayHello(numberOfTimes: Int) -> () {
guard numberOfTimes > 0 else {
return
}
for _ in 1...numberOfTimes {
print("Hello!")
}
}
guard
语句中的 else
分支必须退出当前的区域,通过使用 return
来退出函数,continue
或者 break
来退出循环,或者使用像 fatalError(_:file:line:)
这种返回 Never
的函数。
使用 guard 来避免过多的缩进和错误
比如,我们要实现一个 readBedtimeStory() 函数:
enum StoryError:Error {
case missing
case illegible
case tooScary
}
//未使用guard的函数
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
}
}
要读一个睡前故事,我们需要能找到一本书,这本故事书必须要是可读的,并且故事不能太吓人(请不要让怪物出现在书的结尾,谢谢你!)。
请注意 throw 语句离检查本身有多远。你需要读完整个方法来找到如果没有 book.txt 会发生什么。
像一本好书一样,代码应该讲述一个故事:有着易懂的情节,清晰的开端、发展和结尾。(请尝试不要写太多「后现代」风格的代码。)
使用 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)")
}
这样就好多了! 每一个错误都在相应的检查之后立刻被抛出,所以我们可以按照左手边的代码顺序来梳理工作流的顺序。
defer
defer
关键字为此提供了安全又简单的处理方式:声明一个 block,当前代码执行的闭包退出时会执行该 block。
看看下面这个包装了系统调用 gethostname(2)
的函数,用来返回当前系统的主机名称:
import Darwin
func currenthostName() -> String {
let capacity = Int(NI_MAXHOST)
let buffer = UnsafeMutablePointer.allocate(capacity: capacity)
guard gethostname(buffer, capacity) == 0 else {
buffer.deallocate()
return "localhost"
}
let hostname = String(cString: buffer)
buffer.deallocate()
return hostname
}
这里有一个在最开始就创建的 UnsafeMutablePointer
这种设计很容易导致错误,而且不停地在做重复工作。
通过使用 defer 语句,我们可以排除潜在的错误并且简化代码:
func currentHostName2() -> String {
let capacity = Int(NI_MAXHOST)
let buffer = UnsafeMutablePointer.allocate(capacity: capacity)
defer {
buffer.deallocate()
}
guard gethostname(buffer, capacity) == 0 else {
return "localhost"
}
return String(cString: buffer)
}
尽管 defer 紧接着出现在 allocate(capacity:) 调用之后,但它要等到当前区域结束时才会被执行。多亏了 defer,buffer 才能无论在哪个点退出函数都可以被释放。
考虑在任何需要配对调用的 API 上都使用 defer
,比如 allocate(capacity:) / deallocate()、wait() / signal() 和 open() / close()。
如果在 defer 语句中引用了一个变量,执行时会用到变量最终的值。换句话说:defer 代码块不会捕获变量当前的值。
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/