斯威夫特的后卫关键字


197

Swift 2引入了guard关键字,该关键字可用于确保已准备好配置各种数据。我在此网站上看到的示例演示了一个commitTapped函数:

func submitTapped() {
    guard username.text.characters.count > 0 else {
        return
    }

    print("All good")
}

我想知道使用条件guard是否与使用老式方法使用if条件有所不同。它会带来好处吗?使用简单的支票无法获得这些好处?


另请参阅后卫对if-let的问题
亲爱的

Answers:


368

阅读本文时,我注意到使用Guard的巨大好处

在这里,您可以将guard的使用与一个示例进行比较:

这是没有防护的部分:

func fooBinding(x: Int?) {
    if let x = x where x > 0 {
        // Do stuff with x
        x.description
    }

    // Value requirements not met, do something
}
  1. 在这里,您将所需的代码置于所有条件下

    您可能不会立即看到此问题,但是您可以想象,如果在执行语句之前嵌套了许多必须满足的条件,那么它会变得多么混乱

清理的方法是先进行每项检查,然后在未满足要求时退出。这样可以轻松了解导致此功能退出的条件。

但是现在我们可以使用Guard了,我们可以看到可以解决一些问题:

func fooGuard(x: Int?) {
    guard let x = x where x > 0 else {
        // Value requirements not met, do something
        return
    }

    // Do stuff with x
    x.description
}
  1. 检查您想要的条件,而不是您不想要的条件。这又类似于断言。如果不满足条件,则运行guard的else语句,该语句会中断该功能。
  2. 如果条件通过,此处的可选变量将在调用Guard语句的范围内自动为您解包-在这种情况下,为fooGuard(_ :)函数。
  3. 您正在及早检查不良情况,使您的功能更具可读性,更易于维护

对于非可选值,同样的模式也适用:

func fooNonOptionalGood(x: Int) {
    guard x > 0 else {
        // Value requirements not met, do something
        return
    }

    // Do stuff with x
}

func fooNonOptionalBad(x: Int) {
    if x <= 0 {
        // Value requirements not met, do something
        return
    }

    // Do stuff with x
}

如果您还有任何疑问,可以阅读整篇文章:Swift Guard声明。

包起来

最后,通过阅读和测试,我发现如果您使用保护罩来打开任何可选组件,

这些未包装的值留在您的代码块的其余部分中供您使用

guard let unwrappedName = userName else {
    return
}

print("Your username is \(unwrappedName)")

在这里,展开的值仅在if块内可用

if let unwrappedName = userName {
    print("Your username is \(unwrappedName)")
} else {
    return
}

// this won't work – unwrappedName doesn't exist here!
print("Your username is \(unwrappedName)")

3
嘿@Eric,您的帖子很棒!感谢您使它变得如此易于理解!
Jorge Casariego

1
我正在使用用于包装NSError的保护。但是,当我尝试在保护范围内使用它时(例如,为了将错误传递给某些回调),它说“在保护条件下声明的变量在其主体中不可用”。有道理吗?谢谢
GeRyCh

6
在保护声明中展开@GeRyCh可使该变量在保护声明之后而不是在内部可用。花了我一段时间来习惯这一点。
DonnaLea 2015年

2
这是另一篇优秀的文章,介绍了如何使用guard干净地包装可选组件。很好地总结一下。
Doches

let x = x where x > 0这是否意味着您将另一个条件耦合到了可选绑定中?我的意思是与if let constantName = someOptional { statements }
亲爱的,

36

与不同ifguard创建可从其块外部访问的变量。拆开很多Optionals 很有用。


24

确实有两大好处guard。正如其他人所提到的,其中之一是避免陷入厄运金字塔– if let彼此嵌套的许多烦人的陈述越来越向右移动。

另一个好处是,您通常想实现的逻辑是“ if not let”而不是“if let { } else ”。

这是一个示例:假设您要实现accumulate–交叉点map和交叉点reduce给您带来了一系列运行的 reduces。这是与guard

extension Sliceable where SubSlice.Generator.Element == Generator.Element {

    func accumulate(combine: (Generator.Element,Generator.Element)->Generator.Element) -> [Generator.Element] {
        // if there are no elements, I just want to bail out and
        // return an empty array
        guard var running = self.first else { return [] }

        // running will now be an unwrapped non-optional
        var result = [running]

        // dropFirst is safe because the collection
        // must have at least one element at this point
        for x in dropFirst(self) {
            running = combine(running, x)
            result.append(running)
        }
        return result
    }

}


let a = [1,2,3].accumulate(+)  // [1,3,6]
let b = [Int]().accumulate(+)  // []

您如何在没有保护的情况下编写它,但是仍然使用first它返回一个可选内容?像这样:

extension Sliceable where SubSlice.Generator.Element == Generator.Element {

    func accumulate(combine: (Generator.Element,Generator.Element)->Generator.Element) -> [Generator.Element] {

        if var running = self.first  {
            var result = [running]

            for x in dropFirst(self) {
                running = combine(running, x)
                result.append(running)
            }
            return result
        }
        else {
            return []
        }
    }

}

多余的嵌套很烦人,但是,将ifand和elseso分开那么远也不合逻辑。尽早为空的情况退出,然后继续执行该函数的其余部分,似乎是不可能的,这更具可读性。


19

当使用条件满足时,guard它将在guard块中声明的变量暴露给其余的代码块,从而将它们纳入其范围。如前所述,其中的嵌套if let语句肯定会派上用场。

请注意,后卫需要在其else语句中返回抛出

使用Guard解析JSON

下面是一个示例,说明了如何使用guard而不是if-let解析JSON对象。这是博客条目的摘录,其中包括一个游乐场文件,您可以在这里找到:

如何在Swift 2中使用Guard解析JSON

func parseJSONWithGuard(data : [String : AnyObject]) throws -> Developer {

    guard let firstname = data["First"] as? String  else {
        return Developer() // we could return a nil Developer()
    }

    guard let lastname = data["Last"] as? String else {
        throw ParseError.BadName // or we could throw a custom exception and handle the error
    }

    guard let website = data["WebSite"] as? String else {
        throw ParseError.BadName
    }

    guard let iosDev = data["iosDeveloper"] as? Bool else {
        throw ParseError.BadName
    }



    return Developer(first: firstname, last: lastname, site: website, ios: iosDev)

}

下载游乐场:守卫游乐场

更多信息:

这是《The Swift Programming Language Guide》的节选

如果满足了保护语句的条件,则在保护语句的右括号之后代码将继续执行。使用条件绑定(作为条件的一部分)为值分配了值的任何变量或常量,对于该保护语句所在的其余代码块均可用。

如果不满足该条件,则执行else分支内的代码。该分支必须转移控制以退出该保护声明出现的代码块。它可以通过控制转移语句(例如return,break或Continue)来完成此操作,或者可以调用不返回的函数或方法,例如作为fatalError()。


7

好处之一是消除了许多嵌套if let语句。观看15:30左右的WWDC“ Swift的新功能”视频,标题为“厄运金字塔”。


6

何时使用警卫

如果您的视图控制器包含一些UITextField元素或某些其他类型的用户输入,则您会立即注意到,必须拆开textField.text可选项才能获取其中的文本(如果有!)。在没有任何输入的情况下,isEmpty不会对您有任何帮助,文本字段将仅返回nil。

因此,您需要解开其中的一些内容,最后将其传递给将它们发布到服务器端点的函数。我们不希望服务器代码必须处理nil值或错误地将无效值发送到服务器,因此我们将首先使用guard来包装这些输入值。

func submit() {
    guard let name = nameField.text else {
        show("No name to submit")
        return
    }

    guard let address = addressField.text else {
        show("No address to submit")
        return
    }

    guard let phone = phoneField.text else {
        show("No phone to submit")
        return
    }

    sendToServer(name, address: address, phone: phone)
}

func sendToServer(name: String, address: String, phone: String) {
  ...
}

您会注意到,我们的服务器通信功能将非可选的String值用作参数,因此保护程序会预先展开。解包有点不直观,因为我们习惯于使用if let来解包在块内使用的值。这里的guard语句有一个关联的块,但实际上是else块-即,如果解包失败,您将执行的操作-值将被直接解包到与语句本身相同的上下文中。

// 关注点分离

没有护卫

如果不使用防护措施,我们最终会得到一大堆类似于毁灭金字塔的代码。在向我们的表单添加新字段或编写易于阅读的代码时,这种方法不能很好地扩展。缩进可能很难遵循,尤其是在每个fork都有很多其他语句的情况下。

func nonguardSubmit() {
    if let name = nameField.text {
        if let address = addressField.text {
            if let phone = phoneField.text {
                sendToServer(name, address: address, phone: phone)
            } else {
                show("no phone to submit")
            }
        } else {
            show("no address to submit")
        }
    } else {
        show("no name to submit")
    }
}

是的,我们甚至可以将所有这些if语句组合成一个用逗号分隔的语句,但是我们将失去找出哪个语句失败并向用户显示消息的能力。

https://thatthinginswift.com/guard-statement-swift/


5

使用防护罩,我们的意图很明显。如果不满足特定条件,我们不想执行其余代码。在这里我们也可以扩展链,请看下面的代码:

guard let value1 = number1, let value2 = number2 else { return }
 // do stuff here

5

警卫声明要去做。这是几个不同的

1)它允许我减少嵌套的if语句
2)它增加了我的变量可访问的范围

如果声明

func doTatal(num1 : Int?, num2: Int?) {
  // nested if statement
    if let fistNum = num1 where num1 > 0 {
        if let lastNum = num2 where num2 < 50 {

          let total = fistNum + lastNum
        }
    }
 // don't allow me to access out of the scope 
 //total = fistNum + lastNum 
}

警卫声明

func doTatal(num1 : Int?, num2: Int?) {
   //reduce  nested if statement and check positive way not negative way 
    guard let fistNum = num1 where num1 > 0 else{
      return
    }
    guard  let lastNum = num2 where num2 < 50 else {
     return
    }
    // increase my scope which my variable accessible
    let total = fistNum + lastNum

}

3

从Apple文档中:

警卫声明

如果不满足一个或多个条件,则使用保护声明将程序控制移出范围。

Synatx:

guard condition else {
    statements
}

优点:

通过使用guard语句,我们可以摆脱深层嵌套的条件,其唯一目的是验证一组需求。

2.专为早日退出方法或功能而设计。

如果使用下面的代码,则代码看起来如何。

  let task = URLSession.shared.dataTask(with: request) { (data, response, error) in

        if error == nil {
            if let  statusCode = (response as? HTTPURLResponse)?.statusCode, statusCode >= 200 && statusCode <= 299 {
                if let data = data {

                    //Process Data Here.
                    print("Data: \(data)")

                } else {
                    print("No data was returned by the request!")
                }
            } else {
                print("Your request returned a status code other than 2XX!")
            }
        } else {
            print("Error Info: \(error.debugDescription)")
        }
    }
    task.resume()

如果不满足一个或多个条件,可以使用Guard将控制权移出作用域。

let task = URLSession.shared.dataTask(with: request) { (data, response, error) in

            /* GUARD: was there an error? */
            guard (error == nil) else {
                print("There was an error with your request: \(error)")
                return
            }

            /* GUARD: Did we get a successful 2XX response? */
            guard let statusCode = (response as? HTTPURLResponse)?.statusCode, statusCode >= 200 && statusCode <= 299 else {
                print("Your request returned a status code other than 2XX!")
                return
            }

            /* GUARD: was there any data returned? */
            guard let data = data else {
                print("No data was returned by the request!")
                return
            }

            //Process Data Here.
            print("Data: \(data)")
}
task.resume()

参考:

1. 斯威夫特2:早早带卫兵 2. 勇敢 3. 卫兵声明


但是您可以使用if语句来完成后者吗?if condition { return }
奥利弗·迪克森

2

类似于if语句,guard会根据表达式的布尔值执行语句。与if语句不同,guard语句仅在不满足条件的情况下运行。您可以将警卫更像是断言,但可以崩溃而不是崩溃。

请参阅:http : //ericcerney.com/swift-guard-statement/


1

它确实确实使具有多个查找和可选项的序列流更加简洁明了,并减少了很多嵌套。有关替换Ifs的信息请参见Erica Sadun帖子。....可能会被带走,下面是一个例子:

    let filteredLinks = locationsLinkedToList.filter({$0.actionVerb == movementCommand})
    guard let foundLink = filteredLinks.first else {return ("<Person> cannot go in that direction.", nil, nil)}
    guard filteredLinks.count == 1 else {return ("<Person> cannot decide which route to take.", nil, nil)}
    guard let nextLocation = foundLink.toLocation else {return ("<Person> cannot go in that direction.", nil, nil)}

看看是否坚持。


1

简而言之,它提供了一种在执行之前验证字段的方法。这是一种很好的编程风格,因为它提高了可读性。在其他语言中,它可能看起来像这样:

func doSomething() {
    if something == nil {
        // return, break, throw error, etc.
    }
    ...
}

但是由于Swift为您提供了可选选项,因此我们无法检查它是否为nil 并将其值分配给变量。相反,if let检查它是否不是nil并分配一个变量来保存实际值。这就是guard发挥作用的地方。它为您提供了一个更简洁的退出方式,可以使用可选选项尽早退出。


1

资料来源:Swift中的Guard

让我们看一下示例以清楚地了解它

范例1:

func validate() {         
    guard 3>2 else {             
    print ("False")             
    return         
    }         
    print ("True") //True     
} 
validate()

在上面的示例中,我们看到3大于2,并且guard else子句中的语句被跳过,并输出True。

范例2:

func validate() {         
    guard 1>2 else {             
    print ("False")            //False 
    return         
    }         
    print ("True")      
} 
validate()

在上面的示例中,我们看到1小于2,并且执行了else else子句中的语句,并输出False和return。

Example 3: gaurd let, unwrapping optionals through guard let

func getName(args myName: String?) {
     guard let name = myName, !name.isEmpty else {
     print ("Condition is false")          // Condition is false            return         
     }         
     print("Condition is met\(name)")     
} 
getName(args: "")

在上面的示例中,我们使用guard let来解开可选选项。在函数getName中,我们定义了一个字符串类型myName的变量,该变量是可选的。然后,我们使用guard let检查变量myName是否为nil,如果未分配给name并再次检查,则name不为空。如果两个条件均符合,即为true,则else块将被跳过,并显示“条件满足名称”。

基本上,我们在这里检查用逗号分隔的两件事,首先是拆包和可选,然后检查是否满足条件。

在这里,我们没有向函数传递任何内容,即空字符串,因此print为condition为false。

func getName(args myName: String?) {
     guard let name = myName, !name.isEmpty else {
     print ("Condition is false")          
     return         
     }        
     print("Condition is met \(name)") // Condition is met Hello    
} getName(args: "Hello")

在这里,我们将“ Hello”传递给函数,您可以看到输出显示为“满足条件Hello”。

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.