【问题标题】:Swift: Testing optionals for nilSwift:测试 nil 的可选项
【发布时间】:2014-09-25 16:33:36
【问题描述】:

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

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

if (xyz) // Do something

if (xyz != nil) // Do something

文档说是第一种方式,但我发现有时需要第二种方式,不会产生编译器错误,但其他时候,第二种方式会产生编译器错误。

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

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

if (xmlError != nil)

在这里,如果我刚刚这样做:

if xmlError

它总是会返回 true。但是,如果我这样做:

if (xmlError != nil)

然后它就可以工作了(就像它在 Objective-C 中的工作方式一样)。

GData XML 以及它处理我缺少的选项的方式有什么问题吗?

【问题讨论】:

  • 能否为您的意外情况和错误情况提供完整示例,好吗? (而且我知道这很难,但试着开始去掉条件句周围的括号!)
  • 这在 Xcode 6 beta 5 中有所改变
  • 我刚刚用意外案例更新了问题。
  • 我还没有更新到 beta 5。我很快就会这样做;很高兴见到 ??在 Swift 中,以及更一致的可选行为。

标签: ios swift optional


【解决方案1】:

在 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`.
}

【讨论】:

  • 有人能详细说明为什么 xyz == nil 不起作用吗?
  • 第二个例子应该这样写: // 使用 xy 做某事(这是两个变体在用例上的主要区别)
  • @Christoph:错误消息是在初始化之前使用的常量“xyz”。我认为您需要在 xyz 声明行的末尾附加“= nil”。
  • 对于那些像我一样不熟悉 swift 并且想知道的人:您仍然必须在 if 块中解开 xyz。不过它是安全的,即使你强制展开
  • @Omar 这就是第二种形式的美妙之处,您可以在块内使用 xy 而无需打开它。
【解决方案2】:

要添加到其他答案,而不是在 if 条件内分配给不同命名的变量:

var a: Int? = 5

if let b = a {
   // do something
}

您可以像这样重用相同的变量名:

var a: Int? = 5

if let a = a {
    // do something
}

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

这利用了 Swift 支持的variable shadowing

【讨论】:

    【解决方案3】:

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

    假设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。在将其视为一个变量之前,请确保您正在使用的变量是一个可选变量。

    【讨论】:

    • 我觉得这种方法很糟糕,因为我不必要地创建了一个新变量(常量),并且不得不创建一个新名称,大多数情况下会比以前更糟糕。花费我更多的时间来写作和为读者服务。
    • 这是解包可选变量的标准方法和推荐方法。 “'if let” 非常强大。
    • @gnasher729 但是如果你只想使用 else 部分怎么办
    【解决方案4】:

    Swift 3.0、4.0

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

    1.如果让

    if let 是检查 optional 是否为零的最基本方法。其他条件可以附加到这个 nil 检查,用逗号分隔。变量不能为 nil 才能移动到下一个条件。如果只需要 nil 检查,则删除以下代码中的额外条件。

    除此之外,如果x 不为零,则 if 闭包将被执行,x_val 将在内部可用。否则触发 else 闭包。

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

    2。守护让

    guard let 可以做类似的事情。它的主要目的是使它在逻辑上更合理。这就像说确保变量不为零,否则停止函数guard let 也可以像if let 那样做额外的条件检查。

    不同之处在于解包后的值将在与guard let 相同的范围内可用,如下面的评论所示。这也导致在 else 闭包中,程序必须退出当前范围,returnbreak 等。

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

    【讨论】:

      【解决方案5】:

      来自迅捷编程guide

      If 语句和强制解包

      您可以使用 if 语句来确定一个可选项是否包含 价值。如果一个可选项确实有一个值,它的计算结果为真;如果它 根本没有任何价值,它的计算结果为假。

      所以最好的方法是

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

      如果你在 if 语句中使用 xyz。那么你可以在常量变量中的 if 语句中解开 xyz。所以你不需要解开 if 语句中使用 xyz 的每个地方。

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

      apple 建议使用此约定,开发人员将遵循此约定。

      【讨论】:

      • 在 Swift 3 中你必须做if xyz != nil {...}
      • 在 Swift 3 中,选项不能用作布尔值。首先是因为它是一种 C 主义,本来就不应该出现在该语言中,其次是因为它会导致与可选 Bool 的绝对混淆。
      【解决方案6】:

      尽管您仍然必须明确地将可选项与 nil 进行比较,或者使用可选项绑定来额外提取其值(即可选项不会隐式转换为布尔值),但值得注意的是 Swift 2 已将 guard statement 添加到使用多个可选值时有助于避免pyramid of doom

      换句话说,您现在的选项包括显式检查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
      

      【讨论】:

      • 如上所述,您也可以这样做以摆脱嵌套的可选状态。如果让 abc = abc?.xyz?.something { }
      • @Suhaib 啊,是的:您还可以使用可选链接 (developer.apple.com/library/prerelease/content/documentation/…) 快速访问嵌套属性。我没有在这里提到它,因为我认为它可能与最初的问题相去甚远。
      • @turingtested 谢谢!实际上,我认为这个 对程序员很友好,因为它保证您不会意外尝试使用 nil 值,但我同意学习曲线有点陡峭。 :)
      【解决方案7】:

      一个没有特别提到的选项是使用 Swift 的忽略值语法:

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

      我喜欢这样,因为在 Swift 这样的现代语言中检查 nil 感觉格格不入。我认为它感觉不合适的原因是nil 基本上是一个哨兵值。在现代编程中,我们几乎在其他任何地方都取消了哨兵,所以nil 感觉也应该这样做。

      【讨论】:

        【解决方案8】:

        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
        }
        

        【讨论】:

        • 这可能是pythonistasswift 类似物,它试图使语言保持某种形式的pythonic 纯度。我的看法?我刚刚将你的代码提升到我的 Utils mixin 中。
        【解决方案9】:

        当您想根据某物是否为 nil 来获取值时,三元运算符可能会派上用场,而不是 if

        func f(x: String?) -> String {
            return x == nil ? "empty" : "non-empty"
        }
        

        【讨论】:

        • xyz == nil 表达式不起作用,但 x != nil 起作用。不知道为什么。
        【解决方案10】:

        除了使用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
        }
        

        您可能应该使用 ifguard,因为它们是 Swift 程序员使用的最传统(因此很熟悉)的方法。

        【讨论】:

          【解决方案11】:

          可选

          你也可以使用Nil-Coalescing Operator

          如果nil-coalescing operator (a ?? b) 包含a 值,则nil-coalescing operator (a ?? b) 展开可选a,如果anil,则返回a 默认值b。表达式 a 始终是可选类型。表达式b 必须与存储在a 中的类型匹配。

          let value = optionalValue ?? defaultValue
          

          如果optionalValuenil,它会自动将值分配给defaultValue

          【讨论】:

          • 我喜欢这样,因为它提供了指定默认值的简单选项。
          【解决方案12】:

          现在您可以快速执行以下操作,让您重新获得一点目标-c if nil else

          if textfieldDate.text?.isEmpty ?? true {
          
          }
          

          【讨论】:

            【解决方案13】:
            var xyz : NSDictionary?
            
            // case 1:
            xyz = ["1":"one"]
            // case 2: (empty dictionary)
            xyz = NSDictionary() 
            // case 3: do nothing
            
            if xyz { NSLog("xyz is not nil.") }
            else   { NSLog("xyz is nil.")     }
            

            此测试在所有情况下都按预期工作。 顺便说一句,你不需要括号()

            【讨论】:

            • OP关注的案例是:if (xyz != nil).
            【解决方案14】:

            如果你有条件并且想要展开和比较,如何利用复合布尔表达式的短路评估,如

            if xyz != nil && xyz! == "some non-nil value" {
            
            }
            

            当然,这不像其他一些建议的帖子那样可读,但可以完成工作并且比其他建议的解决方案更简洁。

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2014-11-30
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多