【问题标题】:swift, optional unwrapping, reversing if condition快速,可选展开,反转 if 条件
【发布时间】:2014-12-05 22:50:31
【问题描述】:

假设我有一个返回可选的函数。如果错误则为零,如果成功则返回值:

func foo() -> Bar? { ... }

我可以使用以下代码来处理这个函数:

let fooResultOpt = foo()
if let fooResult = fooResultOpt {
    // continue correct operations here
} else {
    // handle error
}

但是,对于任何重要的代码,这种方法几乎没有问题:

  1. 最后进行了错误处理,很容易漏掉一些东西。当错误处理代码跟随函数调用时,这会好得多。

  2. 正确的操作代码缩进一级。如果我们要调用另一个函数,我们必须再缩进一次。

使用 C 的人通常可以这样写:

Bar *fooResult = foo();
if (fooResult == null) {
    // handle error and return
}
// continue correct operations here

我找到了两种使用 Swift 实现相似代码风格的方法,但我都不喜欢。

let fooResultOpt = foo()
if fooResult == nil {
    // handle error and return
}
// use fooResultOpt! from here
let fooResult = fooResultOpt! // or define another variable

如果我写“!”到处都是,它看起来不合我的口味。我可以引入另一个变量,但这看起来也不太好。理想情况下,我希望看到以下内容:

if !let fooResult = foo() {
    // handle error and return
}
// fooResult has Bar type and can be used in the top level

我是否遗漏了规范中的某些内容,或者是否有其他方法可以编写好看的 Swift 代码?

【问题讨论】:

    标签: swift error-handling optional


    【解决方案1】:

    您的假设是正确的——在 Swift 中没有“否定 if-let”语法。

    我怀疑其中一个原因可能是语法完整性。在整个 Swift 中(通常在其他受 C 启发的语言中),如果你有一个可以绑定局部符号的语句(即命名新变量并赋予它们值)并且可以有一个块体(例如 if、while、for),那些绑定的范围仅限于所述块。让块语句将符号绑定到其封闭范围将是不一致的。

    不过,这仍然是一件值得考虑的事情——我会推荐 filing a bug,看看 Apple 对此做了什么。

    【讨论】:

      【解决方案2】:

      这就是模式匹配的全部意义所在,也是适合这项工作的工具:

      let x: String? = "Yes"
      
      switch x {
      case .Some(let value):
        println("I have a value: \(value)")
      case .None:
        println("I'm empty")
      }
      

      if-let 表单只是在您不需要两条腿时提供方便。

      【讨论】:

      • 谢谢,但您的解决方案与语法 if (let value = x) { ... } else { ... } 没有区别,并且具有我在问题中提到的所有缺点。跨度>
      【解决方案3】:

      如果您正在编写的是一组执行相同转换序列的函数,例如在处理 REST 调用返回的结果时(检查响应不是 nil、检查状态、检查应用程序/服务器错误、解析响应等),我要做的是创建一个管道,在每个步骤中转换输入数据,最后返回nil 或某种类型的转换结果。

      我选择了 >>> 自定义运算符,它直观地指示数据流,但当然可以随意选择自己的:

      infix operator >>> { associativity left }
      
      func >>> <T, V> (params: T?, next: T -> V?) -> V? {
          if let params = params {
              return next(params)
          }
          return nil
      }
      

      操作符是一个函数,它接收某种类型的值作为输入,以及将值转换为另一种类型的值的闭包。如果值不为零,则函数调用闭包,传递值,并返回其返回值。如果值为nil,则运算符返回nil

      可能需要一个例子,所以假设我有一个整数数组,我想按顺序执行以下操作:

      • 对数组的所有元素求和
      • 计算 2 的幂
      • 除以 5 并返回整数部分和余数
      • 将以上两个数相加

      这是 4 个功能:

      func sumArray(array: [Int]?) -> Int? {
          if let array = array {
              return array.reduce(0, combine: +)
          }
      
          return nil
      }
      
      func powerOf2(num: Int?) -> Int? {
          if let num = num {
              return num * num
          }
          return nil
      }
      
      func module5(num: Int?) -> (Int, Int)? {
          if let num = num {
              return (num / 5, num % 5)
          }
          return nil
      }
      
      func sum(params: (num1: Int, num2: Int)?) -> Int? {
          if let params = params {
              return params.num1 + params.num2
          }
          return nil
      }
      

      这就是我的使用方式:

      let res: Int? = [1, 2, 3] >>> sumArray >>> powerOf2 >>> module5 >>> sum
      

      这个表达式的结果要么是 nil,要么是管道最后一个函数中定义的类型的值,在上面的例子中是 Int

      如果你需要做更好的错误处理,你可以像这样定义一个枚举:

      enum Result<T> {
          case Value(T)
          case Error(MyErrorType)
      }
      

      并将上述函数中的所有选项替换为Result&lt;T&gt;,返回Result.Error()而不是nil

      【讨论】:

      • 感谢您的回答,这是一个好方法,但在一般情况下,它似乎非常非必要且难以扩展和修改。它可能非常适合这种数据流自然的情况。
      • 是的,这不是一种通用的方式,它来自函数式编程。也许这不是您特定问题的正确解决方案,但我希望您将来会发现它有用;-)
      【解决方案4】:

      我找到了一种看起来比其他方法更好的方法,但它以不推荐的方式使用语言功能。

      使用问题中的代码示例:

      let fooResult: Bar! = foo();
      if fooResult == nil {
          // handle error and return
      }
      // continue correct operations here
      

      fooResult 可以用作普通变量,不需要使用“?”要么 ”!”后缀。

      Apple 文档说:

      当一个可选项的值在第一次定义后立即确认存在并且可以肯定地假定在此后的每个点都存在时,隐式展开的可选项很有用。在 Swift 中,隐式解包选项的主要用途是在类初始化期间,如 Unowned References 和 Implicitly Unwrapped Optional Properties 中所述。

      【讨论】:

        【解决方案5】:

        以下内容如何:

        func foo(i:Int) ->Int? {
            switch i {
            case 0: return 0
            case 1: return 1
            default: return nil
            }
        }
        
        var error:Int {
            println("Error")
           return 99
        }
        
        for i in 0...2 {
            var bob:Int = foo(i) ?? error
            println("\(i) produces \(bob)")
        }
        

        结果如下:

        0 produces 0
        1 produces 1
        Error
        2 produces 99
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2015-08-20
          • 2021-09-30
          • 2019-01-19
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2021-08-29
          相关资源
          最近更新 更多