斯威夫特: 守卫让与如果让

我一直在阅读关于 Swift 中的 Optionals 的文章,我看到过一些例子,其中使用 if let来检查一个 Options 是否保存了一个值,如果确实保存了一个值,则使用取消包装的值进行处理。

然而,我看到在 Swift 2.0中,关键字 guard let是最常用的。我想知道 if let是否已经从 Swift 2.0中删除,或者它是否仍然可以使用。

我是否应该将包含 if let的程序更改为 guard let

112926 次浏览

if letguard let的用途相似,但不同。

guard的“ else”情况必须退出当前范围。一般来说,这意味着它必须调用 return或中止程序。guard用于提供早期返回,而不需要嵌套函数的其余部分。

if let嵌套它的范围,并且不需要任何特殊的东西。它可以 return也可以不需要。

一般来说,如果 if-let块是函数的其余部分,或者它的 else子句中包含 return或者终止,那么应该使用 guard。这通常意味着(至少在我的经验中) ,当有疑问时,guard通常是更好的答案。但在许多情况下,if let仍然是合适的。

什么时候使用 if-let和什么时候使用 guard通常是一个风格问题。

假设您有 func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int和一个可选的项数组(var optionalArray: [SomeType]?) ,如果数组是 nil(未设置) ,则需要返回 0; 如果数组有值(已设置) ,则需要返回 count

您可以像这样使用 if-let实现它:

func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int
{
if let array = optionalArray {
return array.count
}
return 0
}

或者像这样使用 guard:

func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int
{
guard let array = optionalArray else {
return 0
}
return array.count
}

这些例子在功能上是相同的。

guard真正发光的地方是当你有一个任务,如验证数据,你想函数失败,如果有什么错误。

当您接近完成验证时,不需要嵌套一堆 if-let,“成功路径”和现在成功绑定的选项都在方法的主范围内,因为失败路径已经全部返回。

我将尝试用一些(未优化的)代码解释 Guard 语句的有用性。

您有一个用户界面,您正在验证文本字段用户注册的名字,姓氏,电子邮件,电话和密码。

如果任何 textField 不包含有效的文本,它应该使该字段 firstResponder。

以下是未经优化的代码:

//pyramid of doom


func validateFieldsAndContinueRegistration() {
if let firstNameString = firstName.text where firstNameString.characters.count > 0{
if let lastNameString = lastName.text where lastNameString.characters.count > 0{
if let emailString = email.text where emailString.characters.count > 3 && emailString.containsString("@") && emailString.containsString(".") {
if let passwordString = password.text where passwordString.characters.count > 7{
// all text fields have valid text
let accountModel = AccountModel()
accountModel.firstName = firstNameString
accountModel.lastName = lastNameString
accountModel.email = emailString
accountModel.password = passwordString
APIHandler.sharedInstance.registerUser(accountModel)
} else {
password.becomeFirstResponder()
}
} else {
email.becomeFirstResponder()
}
} else {
lastName.becomeFirstResponder()
}
} else {
firstName.becomeFirstResponder()
}
}

您可以在上面看到,所有字符串(firstNameString、 lastNameString 等)只能在 if 语句的范围内访问。因此它创建了这个“厄运金字塔”,并且有很多问题,包括可读性和易于移动的东西(如果字段顺序改变,你必须重写大部分代码)

使用 Guard 语句(在下面的代码中) ,您可以看到这些字符串在 {}之外是可用的,并且在所有字段都有效的情况下可以使用它们。

// guard let no pyramid of doom
func validateFieldsAndContinueRegistration() {


guard let firstNameString = firstName.text where firstNameString.characters.count > 0 else {
firstName.becomeFirstResponder()
return
}
guard let lastNameString = lastName.text where lastNameString.characters.count > 0 else {
lastName.becomeFirstResponder()
return
}
guard let emailString = email.text where
emailString.characters.count > 3 &&
emailString.containsString("@") &&
emailString.containsString(".") else {
email.becomeFirstResponder()
return
}
guard let passwordString = password.text where passwordString.characters.count > 7 else {
password.becomeFirstResponder()
return
}


// all text fields have valid text
let accountModel = AccountModel()
accountModel.firstName = firstNameString
accountModel.lastName = lastNameString
accountModel.email = emailString
accountModel.password = passwordString
APIHandler.sharedInstance.registerUser(accountModel)
}

如果字段的顺序发生变化,只需上下移动各自的代码行,就可以了。

这是一个非常简单的解释和用例。希望这有所帮助!

保安可以提高 清晰

当您使用 Guard 时,对于 成功来说,您有一个很大的 再高点预期值,而且有些重要的是,如果它不能成功,那么您只想退出范围 很早。就像你保护一个文件/图像是否存在,数组是否为空。

func icon() -> UIImage {
guard let image = UIImage(named: "Photo") else {
return UIImage(named: "Default")! //This is your fallback
}
return image //-----------------you're always expecting/hoping this to happen
}

如果你使用 If-let 编写上面的代码,那么它会向阅读的开发人员传达一个更大的50-50的比例。但是如果你使用 Guard,你可以在代码中添加 清晰,这意味着我希望它在95% 的情况下可以正常工作... ... 如果它失败了,我不知道为什么会这样; 这是非常不可能的... ... 但是接下来只需要使用这个默认映像,或者可能只是用一个有意义的消息来描述哪里出错了!

  • guard产生副作用时,要避免使用防护罩作为 自然流。当 else子句引入副作用时避免防护。 保护建立代码正确执行的 需要条件, 提供早期退出服务

  • 在正分支中执行重要计算时,将 if重构为 guard语句并返回回退值 在 else条款中

来自: 艾丽卡 · 萨顿的《斯威夫特风格》一书

另外,由于上面的建议和干净的代码,更多的 很有可能将需要添加断言到 失败了约束语句,它只是提高可读性,并使其他开发人员清楚地了解你的期望。

guard​ ​let​ image = ​UIImage​(named: selectedImageName) else { // YESSSSSS
assertionFailure(​"Missing ​​\(​selectedImageName​)​​ asset"​)
return
}


guard​ ​let​ image = ​UIImage​(named: selectedImageName) else { // NOOOOOOO
​     ​return
}

来自: 艾瑞卡 · 萨顿的《斯威夫特风格》 + 一些修改

(您不会为 if-let使用断言/先决条件,这看起来不太对)

使用警卫也可以帮助你提高清晰度的 避开金字塔的厄运。参见 Nitin 的回答

Guard 将处理违规需求的代码保存在需求旁边

需要明确的是,guard并不总是关于成功与失败。更通用的方法是处理违反的需求,而不是没有违反的过程代码。

例如:

func getImage(completion: (image: Image)? -> Void) {
guard cache["image1"] == false else {
completion(cache["image1"]!)
}
downloadAndStore("image1") { image in
completion(image)
}
}


在上面的要求中,映像不能出现在缓存中。如果图像存在,那么就违反了我们的要求。我们提早回来。正如您所看到的,我们还处理了违反的代码路径,就在它的需求旁边,也就是说,结构不是:

if requirement {
.
.
ten lines of code
.
.
} else {
handle requirement
}

控制流的 Swift 文档解释了其背后的理念:

对需求使用保护语句可以提高 与使用 if 语句进行相同的检查相比,您的代码。

  • 它允许您编写通常执行的代码,而无需将其包装在 else 块中
  • 它允许您将处理违规需求的代码放在需求旁边。

保护通过在当前范围内创建 新的变量来避免嵌套

有一个重要的区别,我相信没有人解释得很好。

然而,guard letif let都是 打开变量

使用 guard let时,正在创造是一个新变量,它将 存在放在当前作用域中。

对于 if let,您只需要创建一个新的变量 在里面代码块。

guard let:

func someFunc(blog: String?) {
    

guard let blogName = blog else {
print("some ErrorMessage")
print(blogName) // will create an error Because blogName isn't defined yet
return
}
print(blogName) // You can access it here ie AFTER the guard statement!!
    

//And if I decided to do 'another' guard let with the same name ie 'blogName' then I would create an error!
guard let blogName = blog else { // errorLine: Definition Conflicts with previous value.
print(" Some errorMessage")
return
}
print(blogName)
}

if-let:

func someFunc(blog: String?) {
    

    

if let blogName1 = blog {
print(blogName1) // You can only access it inside the code block. Outside code block it doesn't exist!
}
if let blogName1 = blog { // No Error at this line! Because blogName only exists inside the code block ie {}
print(blogName1)
}
}

有关 if let的更多信息,请参见: 为什么重新声明可选绑定不会产生错误


警卫需要 瞄准镜出口

(Rob Napier 的回答中也提到了) :

你必须让 guard在里面定义一个函数。它的主要目的是中止/返回/退出范围,如果不满足一个条件:

var str : String?


guard let blogName1 = str else {
print("some error")
return // Error: Return invalid outside of a func
}
print (blogName1)

对于 if let,你不需要把它放在任何函数中:

var str : String?
if let blogName1 = str {
print(blogName1) // You don't get any errors!
}

guard vs if

值得注意的是,将这个问题看作 guard let vs if letguard vs if更为恰当。

独立的 if不做任何展开,独立的 guard也不做。请看下面的例子。如果值为 nil,则不会提前退出。没有可选值。如果不满足条件,它就会提前退出。

let array = ["a", "b", "c"]
func subscript(at index: Int) -> String?{
guard index > 0, index < array.count  else { return nil} // exit early with bad index
return array[index]
}

基本区别

守卫让

  1. 早期存在过程从范围
  2. 需要像 returnthrow等现有的作用域。
  3. 创建一个可以在作用域外访问的新变量。

如果允许的话

  1. 无法访问范围之外的内容。
  2. 不需要 return语句,但我们可以写

注意: 两者都用于展开可选变量。

我在 Github 斯威夫特风格指南上看到的最清楚的解释是:

if增加了一定程度的深度:

if n.isNumber {
// Use n here
} else {
return
}

guard没有:

guard n.isNumber else {
return
}
// Use n here

警卫

  • guard语句用于将程序控制转移出作用域 如果一个或多个条件没有满足

  • guard语句中的任何条件的值必须是 Bool类型 或桥接到 Bool的类型。该条件也可以是可选的 具有约束力的声明

警卫声明的格式如下:

guard condition else {
//Generally return
}

如果允许的话

  • 可选绑定一样流行。
  • 对于访问可选对象,我们使用 if let
if let roomCount = optionalValue {
print("roomCount available")
} else {
print("roomCount is nil")
}

我跟斯威夫特学的。

典型的 Else-If

 func checkDrinkingAge() {
let canDrink = true


if canDrink {
print("You may enter")
// More Code
// More Code
// More Code


} else {
// More Code
// More Code
// More Code
print("Let me take you to the jail")
}
}

关于 Else-If 的问题

  1. 嵌套括号
  2. 必须读取每一行才能发现错误消息

警卫声明 只有当条件为 false 时,保护块才会运行,它将通过 return 退出函数。如果条件为 true,Swift 将忽略保护块。它提供了早期退出和更少的括号。+

func checkDrinkProgram() {
let iCanDrink = true


guard iCanDrink else {
// if iCanDrink == false, run this block
print("Let's me take you to the jail")
return
}


print("You may drink")
// You may move on
// Come on.
// You may leave
// You don't need to read this.
// Only one bracket on the bottom: feeling zen.
}

用 Else-If 展开可选项

Guard 语句不仅可以用 else-if 语句替换典型的条件块,而且还可以通过最小化方括号的数量来展开选项。为了进行比较,让我们首先开始如何使用 else-if 打开多个选项。 首先,让我们创建三个将被展开的选项。

var publicName: String? = "Bob Lee"
var publicPhoto: String? = "Bob's Face"
var publicAge: Int? = nil

最可怕的噩梦

func unwrapOneByOne() {
if let name = publicName {
if let photo = publicPhoto {
if let age = publicAge {
print("Bob: \(name), \(photo), \(age)")
} else {
print("age is mising")
}
} else {
print("photo is missing")
}
} else {
print("name is missing")
}
}

上面的代码当然可以工作,但是违反了 DRY 原则。这是残忍的。让我们来分析一下。 +

稍微好一点 下面的代码比上面的更具可读性

func unwrapBetter() {
if let name = publicName {
print("Yes name")
} else {
print("No name")
return
}


if let photo = publicPhoto {
print("Yes photo")
} else {
print("No photo")
return
}


if let age = publicAge {
print("Yes age")
} else {
print("No age")
return
}
}

和警卫一起打开 Else-if 语句可以替换为 Guard. +

 func unwrapOneByOneWithGuard() {
guard let name = publicName else {
print("Name missing")
return
}


guard let photo = publicPhoto else {
print("Photo missing")
return
}


guard let age = publicAge else {
print("Age missing")
return
}
print(name)
print(photo)
print(age)
}

使用 else-If 打开多个可选项 到目前为止,你已经一个一个解开了可选项。Swift 允许我们一次打开多个选项。如果其中一个包含 nil,它将执行 else 块。

func unwrap() {
if let name = publicName, let photo = publicPhoto, let age = publicAge {
print("Your name is \(name). I see your face right here, \(photo), you are \(age)")
} else {
// if any one of those is missing
print("Something is missing")
}
}

请注意,当您一次打开多个选项时,您无法识别哪个选项包含 null

用保护打开多个可选项 当然,我们应该使用 Guard over else-if. +

func unwrapWithGuard() {
guard let name = publicName, let photo = publicPhoto, let age = publicAge else {
// if one or two of the variables contain "nil"
print("Something is missing")
return
}


print("Your name is \(name). I see your, \(photo). You are \(age).")
// Animation Logic
// Networking
// More Code, but still zen
}

守卫让与如果让

func anyValue(_ value:String?) -> String {
    

guard let string = value else {
return ""
}
    

return string
}


func anyValue(_ value:String?) -> String {
    

if let string = value {
return string
}
    

return ""
}

“警卫”和“迅速”的 if 语句之间的主要区别是:

  • If 语句用于在满足条件时运行代码。
  • 约束语句用于在不满足条件时运行代码。