Swift:测试可选项是否为nil

172

我正在使用Xcode 6 Beta 4。我遇到了这种奇怪的情况,无法确定如何适当地测试可选项。

如果我有一个可选的xyz,正确的测试方式是什么:

if (xyz) // Do something
或者
if (xyz != nil) // Do something

这些文件说要用第一种方式做,但我发现有时需要用第二种方式,并且不会产生编译错误,但有时第二种方式会产生编译错误。

我的具体示例是使用 GData XML 解析器桥接到 Swift:

let xml = GDataXMLDocument(
    XMLString: responseBody,
    options: 0,
    error: &xmlError);

if (xmlError != nil)

在这里,如果我只是这样做:

if xmlError

它将始终返回true。但是,如果我执行以下操作:

if (xmlError != nil)

那么它可以正常工作(就像在Objective-C中一样)。

我是否遗漏了GData XML及其处理可选项的方式?


4
请给出您意外情况和错误情况的完整示例,谢谢!(我知道这很难,但请尝试不要在条件语句周围加括号!) - Matt Gibson
1
这在Xcode 6 beta 5中已经改变。 - newacct
http://jamesonquave.com/blog/swifts-nil-coaelescing-operator-in-xcode-6-beta-5/ - TonyMkenu
我刚刚更新了这个问题,加入了意料之外的情况。 - tng
我还没有更新到beta 5。我很快会这样做;很高兴在Swift中看到??,以及更一致的可选行为。 - tng
16个回答

208
在 Xcode Beta 5 中,它们不再允许您执行以下操作:
var xyz : NSString?

if xyz {
  // Do something using `xyz`.
}

这会产生一个错误:

不符合协议“BooleanType.Protocol”

您必须使用以下形式之一:

if xyz != nil {
   // Do something using `xyz`.
}

if let xy = xyz {
   // Do something using `xy`.
}

9
有人能详细说明为什么 xyz == nil 不起作用吗? - Christoph
第二个例子应该这样写:// 使用xy执行某些操作(这是两种变体之间用例的主要区别) - Alper
@Christoph:错误信息是常量“xyz”在初始化之前被使用。我认为你需要在声明xyz的行末添加“= nil”。 - user3207158
1
对于像我一样刚接触 Swift 的新手,你可能会想知道:即使在 if 块内部,你仍然需要对 xyz 进行解包。不过这是安全的,即使你强制解包也不会出问题。 - Omar
@Omar,这就是第二种形式的美妙之处,你可以在块内部使用xy,而不必对其进行解包。 - Erik Doernenburg

33

除了其他答案之外,不要在if条件语句中为一个不同命名的变量赋值:

var a: Int? = 5

if let b = a {
   // do something
}

你可以像这样重复使用同一个变量名:

var a: Int? = 5

if let a = a {
    // do something
}

这可能会帮助您避免用完创意变量名称...

利用 变量屏蔽,它在 Swift 中得到支持。


22

Swift 3.0, 4.0

检查可选项是否为nil主要有两种方法。以下是它们之间的比较示例。

1. if let

if let是检查可选项是否为nil的最基本方式。可以在此nil检查后追加其他条件,以逗号分隔。变量必须不为nil才能进入下一个条件。如果仅需要进行nil检查,请在以下代码中删除额外的条件。

除此之外,如果x不为nil,则执行if闭包并且x_val在其中可用。否则,将触发else闭包。

if let x_val = x, x_val > 5 {
    //x_val available on this scope
} else {

}

2. guard let

guard let有类似的功能。它的主要目的是使逻辑更加合理。就像在说确保变量不为nil,否则停止函数guard let也可以像if let一样进行额外的条件检查。

两者的区别在于,在相同的作用域内,已解包的值将在guard let中可用,如下面的注释所示。这也导致在else闭包中,程序必须通过returnbreak等退出当前作用域。

guard let x_val = x, x_val > 5 else {
    return
}
//x_val available on this scope

哇,Swift 的语法确实很独特、很有趣啊!不过总的来说还是很好理解的,谢谢! - Timo Ernst
如果您喜欢这些语法,Golang 也有类似的东西可以在一行中计算和检查条件。它也更通用,可以在许多地方使用(当然不包括移动设备),并且通常运行效率非常高。 - Fangming

20

使用可选项最直接的方法之一如下:

假设xyz是可选类型,例如Int?

if let possXYZ = xyz {
    // do something with possXYZ (the unwrapped value of xyz)
} else {
    // do something now that we know xyz is .None
}

通过这种方式,您可以测试xyz是否包含一个值,如果是,则立即使用该值。

关于编译器错误,类型UInt8不是可选的(注意没有'?'),因此无法转换为nil。在将变量视为可选之前,请确保您正在使用的变量是可选的。


1
我认为这种方法不好,因为我会不必要地创建一个新变量(常量),并且不得不创建一个新名称,大多数情况下它会比以前更糟糕。这需要我花费更多的时间来编写代码,并且对于读者来说也是如此。 - Lombas
3
这是解包可选变量的标准方法和推荐方法。“if let”非常强大。 - gnasher729
@gnasher729 但是如果你只想使用else部分呢? - Bradley Thomas

15

来自swift编程指南(指南链接)

If语句和强制解包

您可以使用if语句查找可选项是否包含值。如果可选项具有值,则其计算结果为true;如果没有任何值,则其计算结果为false。

因此,最佳方法是

// swift > 3
if xyz != nil {}

如果您在if语句中使用了xyz,那么您可以将xyz解包到常量变量中的if语句中。 这样,您就不需要在if语句的每个使用xyz的地方解包。

if let yourConstant = xyz {
      //use youtConstant you do not need to unwrap `xyz`
}

这个惯例是由苹果建议的,将会被开发者遵循。


2
在Swift 3中,您必须执行if xyz != nil {...} - psmythirl
在Swift 3中,选项不能用作布尔值。首先,因为它是C语言的一种表达方式,本来就不应该出现在这门语言中;其次,因为它会与可选的Bool类型造成绝对的混淆。 - gnasher729

9
尽管你仍需显式地将可选项与 nil 进行比较或使用可选绑定来提取其值(即选项不会隐式转换为布尔值),但值得注意的是,Swift 2 增加了 guard 语句 来帮助避免在使用多个可选值时出现 “金字塔式的地狱”

换句话说,您现在的选项包括显式检查 nil

if xyz != nil {
    // Do something with xyz
}

可选绑定:

if let xyz = xyz {
    // Do something with xyz
    // (Note that we can reuse the same variable name)
}

还有 guard 语句:

guard let xyz = xyz else {
    // Handle failure and then exit this code block
    // e.g. by calling return, break, continue, or throw
    return
}

// Do something with xyz, which is now guaranteed to be non-nil

请注意,当存在多个可选值时,普通的可选绑定可能会导致更大的缩进:
if let abc = abc {
    if let xyz = xyz {
        // Do something with abc and xyz
    }        
}

您可以使用guard语句避免这种嵌套:

guard let abc = abc else {
    // Handle failure and then exit this code block
    return
}

guard let xyz = xyz else {
    // Handle failure and then exit this code block
    return
}

// Do something with abc and xyz

1
如上所述,您也可以这样做来摆脱嵌套的可选语句。如果让abc = abc?.xyz?.something { } - Suhaib
1
@Suhaib Ah,没错:你也可以使用可选链(https://developer.apple.com/library/prerelease/content/documentation/Swift/Conceptual/Swift_Programming_Language/OptionalChaining.html)快速访问嵌套属性。我没有在这里提到它,因为我认为这可能会偏离原始问题的主题。 - Chris Frederick
@turingtested 谢谢!实际上,我认为这是面向程序员友好的,因为它确保您不会意外地尝试使用 nil 值,但我同意学习曲线有点陡峭。 :) - Chris Frederick

9

Swift 5协议扩展

下面是一种使用协议扩展的方法,使您可以轻松内联可选的nil检查:

import Foundation

public extension Optional {

    var isNil: Bool {

        guard case Optional.none = self else {
            return false
        }

        return true

    }

    var isSome: Bool {

        return !self.isNil

    }

}

用法

var myValue: String?

if myValue.isNil {
    // do something
}

if myValue.isSome {
    // do something
}

2
这可能是Swift中类似于试图保持语言纯粹性的Pythonistas的模拟。我的看法是什么?我只是将你的代码提取到我的Utils混合器中。 - WestCoastProjects

7

还有一种选择,就是使用Swift的忽略值语法:

if let _ = xyz {
    // something that should only happen if xyz is not nil
}

我喜欢这个功能,因为在像Swift这样的现代语言中检查nil感觉很不合适。我认为它感觉不合适的原因是nil基本上是一个哨兵值。在现代编程中,我们已经几乎在所有其他地方放弃了哨兵,所以nil也应该被取消。


6

Swift Optional

除了使用if letguard let语句之外,你还可以使用Nil-Coalescing Operator

nil-coalescing operatora ?? b)会解包可选项a,如果a包含一个值,则返回该值;如果anil,则返回默认值b。表达式a始终是一个可选类型。表达式b必须与存储在a中的类型匹配。

let value = optionalValue ?? defaultValue

如果optionalValue为nil,则自动将值赋给defaultValue。

我喜欢这个,因为它提供了一个简单的选项来指定默认值。 - thowa

4

除了使用ifguard语句进行可选绑定之外,另一种方法是通过扩展Optional来实现:

extension Optional {

    func ifValue(_ valueHandler: (Wrapped) -> Void) {
        switch self {
        case .some(let wrapped): valueHandler(wrapped)
        default: break
        }
    }

}

ifValue接收一个闭包,并在可选项不为nil时将其作为参数调用。它的使用方式如下:

var helloString: String? = "Hello, World!"

helloString.ifValue {
    print($0) // prints "Hello, World!"
}

helloString = nil

helloString.ifValue {
    print($0) // This code never runs
}

然而,作为Swift程序员最常用的方法,您应该使用ifguard


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接