我一直在阅读有关 Swift 中的 Optionals 的内容,并且我看到了一些示例,其中 if let
用于检查 Optional 是否包含一个值,如果它确实存在 - 对未包装的值做一些事情。
然而,我已经看到在 Swift 2.0 中,关键字 guard let
的使用最多。我想知道 if let
是否已从 Swift 2.0 中删除,或者是否仍然可以使用。
我应该将包含 if let
的程序更改为 guard let
吗?
if let
和 guard let
用于相似但不同的目的。
guard
的“else”情况必须退出当前范围。通常这意味着它必须调用 return
或中止程序。 guard
用于提供提前返回,而不需要嵌套函数的其余部分。
if let
嵌套了它的范围,并且不需要任何特殊的东西。它可以return
或不。
一般来说,如果 if-let
块将成为函数的其余部分,或者它的 else
子句将包含 return
或 abort,那么您应该改用 guard
。这通常意味着(至少根据我的经验),当有疑问时,guard
通常是更好的答案。但是在很多情况下 if let
仍然是合适的。
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% 的时间内都能正常工作……如果它失败了,我不知道为什么会这样;这不太可能......但是然后只使用这个默认图像,或者只是用一个有意义的消息来描述问题所在!
当防护装置产生副作用时,请避免防护装置,防护装置将用作自然流动。当 else 子句引入副作用时避免使用守卫。守卫建立代码正确执行所需的条件,提供提前退出当您在正分支中执行大量计算时,将 if 重构为守卫语句并在 else 子句中返回回退值
来自:Erica Sadun 的 Swift Style book
同样由于上述建议和干净的代码,您更有可能希望/需要将断言添加到失败的保护语句中,它只会提高可读性并使其他开发人员清楚您的期望。
guardlet image = UIImage(named: selectedImageName) else { // YESSSSSS assertionFailure("Missing \(selectedImageName)asset") return } guardlet image = UIImage (named: selectedImageName) else { // NOOOOOOO return } 来自:Erica Sadun 的 Swift Style book + 一些修改
(您不会对 if-let
使用断言/前提条件。这似乎不对)
使用警卫还可以通过避免末日金字塔来帮助您提高清晰度。请参阅Nitin's answer。
Guard 通过在当前范围内创建新变量来避免嵌套
我相信没有人能很好地解释一个重要的区别。
但是 guard let
和 if let
unwrap 变量
使用 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
的更多信息,请参阅:Why redeclaration of optional binding doesn't create an error
Guard 需要范围退出
(在 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
,您不需要将它包含在任何 func 中:
var str : String?
if let blogName1 = str {
print(blogName1) // You don't get any errors!
}
守卫与如果
值得注意的是,将此问题视为 guard let
vs if let
和 guard
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]
}
何时使用 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
,而是“成功路径”和现在成功绑定的选项都在方法的主要范围内,因为失败路径都已经返回。
我将尝试用一些(未优化的)代码来解释保护语句的用处。
您有一个 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)
}
如果字段的顺序发生变化,只需向上或向下移动相应的代码行,就可以了。
这是一个非常简单的解释和一个用例。希望这可以帮助!
基本区别
守卫让
范围内的早期存在过程要求范围存在,如返回、抛出等。创建一个可以在范围外访问的新变量。
如果让
范围外无法访问。不需要return语句。但是我们可以写
注意:两者都用于解包 Optional 变量。
守卫让与如果让
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 ""
}
我看到的最清楚的解释是在 Github Swift Style Guide 中:
if
增加了一个深度:
if n.isNumber {
// Use n here
} else {
return
}
guard
不会:
guard n.isNumber else {
return
}
// Use n here
我从 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 的问题
嵌套括号必须阅读每一行才能发现错误消息
保护语句 保护块只有在条件为假的情况下才会运行,并通过 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 解开选项
守卫语句不仅对用 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")
}
}
请注意,当您一次打开多个选项时,您无法确定哪个包含 nil
使用 Guard 解包多个选项当然,我们应该使用 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
}
non-nil
大小写有效时使用if let
。当nil
情况表示某种错误时,使用guard
。guard
也是合适的。有时这只是意味着无事可做。例如,positionTitle
方法可能是guard if let title = title else {return}
。标题可能是可选的,在这种情况下这不是错误。但guard let
仍然合适。if let
绑定的变量仅在 insideif let
范围内可见。guard let
绑定的变量在之后可见。所以使用 guard 来绑定可选值也是有意义的。