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


129

我一直在阅读有关Swift中的Optionals的信息,并且看到了一些示例,这些示例if let用于检查Optional是否持有值,并且在有情况的情况下–使用未包装的值进行操作。

但是,我已经看到在Swift 2.0中,该关键字guard let被广泛使用。我想知道是否if let已从Swift 2.0中删除或是否仍然可以使用它。

我应该改变我的计划包含if letguard let

Answers:


163

if letguard let达到相似但截然不同的目的。

的“其他”情况guard必须退出当前范围。通常,这意味着它必须调用return或中止程序。guard用于提供早期返回,而无需嵌套其余函数。

if let嵌套其作用域,不需要任何特殊的内容。是否可以return

通常,如果该if-let块将成为该函数的其余部分,或者其else子句中包含a return或abort,则应该使用它guard。这通常意味着(至少以我的经验),如果有疑问,guard通常是更好的答案。但是在很多情况下if let仍然合适。


37
大小写有效if let时使用non-nil。使用guard时,nil字母代表某种错误。
BallpointBen17年

4
@BallpointBen我不同意这一点。在很多情况下,guard即使没有错误也很合适。有时,这只是意味着无事可做。例如,一种positionTitle方法可能guard if let title = title else {return}。标题可能是可选的,在这种情况下这不是错误。但是guard let还是合适的。
罗布·纳皮尔

1
是的 我是说让警卫发表评论。
Rob Napier

1
换句话说,当代码有99%的确定不使用else条件时,使用“警卫放手”。另一方面,如果代码为50-50(示例),则使用if条件使用“ if let”。
奇诺·潘

1
绑定的变量if let if let范围内可见。绑定变量guard let可见。因此,使用防护来绑定可选值也很有意义。
boweidmann

104

守卫可以提高清晰度

当您使用Guard时,您对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。但是,如果您使用保护功能,则可以使代码更清晰,这意味着我希望它可以在95%的时间内正常工作……如果它失败了,我不知道为什么会这样。这是不太可能的……但是,请改用此默认图像,或者仅声明一条有意义的消息来说明发生了什么问题!

  • guard当它们产生副作用时,请避免使用,防护措施应作为自然流程使用。当else子句产生副作用时,请避免使用防护措施。警卫队为代码正确执行建立了必要条件,并提早退出

  • 在正分支中执行大量计算时,请从重构if为一条guard语句,并在else子句中 返回回退值

来自: 埃里卡·萨顿(Erica Sadun)的《斯威夫特风格》(Swift Style)书

同样由于上述建议和简洁的代码,您更有可能希望/需要在失败的保护声明中添加断言,这只会提高可读性,并向其他开发人员明确说明您的期望。

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

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

来自: 埃里卡·萨顿(Erica Sadun)的《斯威夫特风格》书和一些修改

(您不会对if-lets 使用asserts / preconditions 。这似乎并不正确)

使用警卫还可以避免金字塔的厄运,从而提高清晰度。请参阅Nitin的答案


Guard创建一个变量

我相信没有一个人能很好地解释这一重要差异。

两者guard letif let 解开变量但是

随着guard let创建一个新的变量,该变量将存在else语句之外。

随着if let没有创建任何新的可变后else语句,只需输入代码块,如果选购的是非零。新创建的变量只存在内部不后的代码块!

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!
}

guardif

值得注意的是,将这个问题视为guard letvs if letguardvs 是更合适的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]
}

45

何时使用if-let以及何时使用guard通常是样式问题。

假设您有func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int一个可选的项数组(var optionalArray: [SomeType]?),并且需要返回0该数组是否为nil(未设置)或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像在完成验证时嵌套一堆s一样,“成功路径”和现在成功绑定的可选参数都在方法的主要范围内,因为故障路径已经全部返回。


29

我将尝试通过一些(未优化的)代码来解释保护语句的有用性。

您有一个UI,您可以在其中验证文本字段以注册用户的名字,姓氏,电子邮件,电话和密码。

如果任何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)
}

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

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


13

基本差异

守卫让

  1. 范围内的早期存在过程
  2. 要求现有分数,例如收益,掷球等。
  3. 创建一个可以访问范围的新变量。

如果让

  1. 无法访问范围。
  2. 无需返回语句。但是我们可以写

注意:两者都用于解包Optional变量。


2

我看到的最清楚的解释是在《Github Swift样式指南》中

if 增加了深度:

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

guard 不:

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

2

守卫

  • guard语句用于一个范围的转移程序控制出如果一个或多个条件没有得到满足。

  • guard语句中任何条件的值都必须是类型Bool 或桥接到的类型Bool。条件也可以是可选的绑定声明。

保护声明的格式如下:

guard condition else {
    //Generally return
}

如果让

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

1

我是和鲍勃一起从中学到的。

典型的否则

 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. 必须阅读每一行以发现错误消息

Guard语句 Guard块仅在条件为false时运行,并且它将通过返回退出函数。如果条件为真,则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解开可选项

保护语句不仅可以用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
                           }
     }

用Guard解包 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解开多个Optionals- 到目前为止,您一直在一个一个地解开Optionals。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")
  }
}

请注意,一次解开多个可选项时,您将无法确定哪个包含nil

使用Guard解开多个可选选项 当然,我们应该对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
}

请返回并修复您的代码格式/缩进!
pkamb
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.