Swift: guard let vs if let

Swift: guard let vs if let

我一直在 Swift 中阅读有关 Optionals 的内容,并且我看到了一些示例,其中 if let 用于检查 Optional 是否包含一个值,如果它包含 - 对展开值。

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

我应该将包含 if let 的程序更改为 guard let 吗?

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

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

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]?),并且您需要 return 或者 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,"success path" 和现在成功绑定的可选值都在方法的主要范围内,因为失败路径已经全部 returned。

我将尝试用一些(未优化的)代码来解释 guard 语句的用处。

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

如果任何 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 语句的范围内访问。所以它创建了这个 "pyramid of doom" 并且它有很多问题,包括可读性和易于移动的东西(如果字段的顺序被改变,你必须重写大部分代码)

使用 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 可以提高清晰度

当您使用 guard 时,您对 guard 的 更高 期望 成功 并且重要的是,如果它不成功成功,那么你只想退出作用域 early。就像你守卫看是否存在 file/image,数组是否为空。

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,你会在你的代码中添加 clarity,这意味着我希望它在 95% 的时间内都能正常工作……如果它失败了,我不知道为什么会这样;这不太可能......但随后只需使用此默认图像代替,或者可能只是用一条有意义的消息断言,描述出了什么问题!

  • Avoid guards when they create side effects, guards are to be used as a natural flow. Avoid guards when else clauses introduce side effects. Guards establish required conditions for code to execute properly, offering early exit

  • When you perform significant computation in the positive branch, refactor from if to a guard statement and returns the fallback value in the else clause

From: Erica Sadun's Swift Style book

此外,由于上述建议和干净的代码,您 更有可能 want/need 将断言添加到 failed guard 语句,它只是提高了可读性,并让其他开发人员清楚你的期望。

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

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

From: Erica Sadun's Swift Style book + some modifications

(你不会用 asserts/preconditions if-let 秒。它看起来不对)

使用守卫还可以通过避免金字塔 厄运来帮助您提高清晰度。参见


Guard 通过在当前范围内创建一个 new 变量来避免嵌套

有一个重要的区别,我相信没有人解释清楚。

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 的更多信息,请参阅:


Guard 需要 范围退出

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

您必须 guard 中定义一个函数。它的主要目的是abort/return/exit范围,如果条件不满足:

var str : String?

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

对于 if let 你不需要把它放在任何 func:

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

guard 对比 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声明。但是我们可以这样写

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

我看到的最清楚的解释是 Github Swift Style Guide:

if 增加一层深度:

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

guard 没有:

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

guard

  • A guard statement is used to transfer program control out of a scope if one or more conditions aren’t met.

  • The value of any condition in a guard statement must be of type Bool or a type bridged to Bool. The condition can also be an optional binding declaration.

保护语句具有以下形式:

guard condition else {
    //Generally return
}

if let

  • Also popular as optional binding.
  • For accessing optional object we use if let.
if let roomCount = optionalValue {
    print("roomCount available")
} else {
    print("roomCount is nil")
}

我从 swift 和 Bob 那里学到了这个..

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

保护声明 保护块仅在条件为假时运行,并且它将通过 return 退出函数。如果条件为真,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
                           }
     }

用 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-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")
  }
}

Be aware that when you unwrap multiple optionals at once, you can't identify which contains nil

使用 Guard 解包多个可选值 当然要用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
}

guard let vs if let

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