展开多个 Swift 选项

Unwrapping multiple Swift optionals

我正在尝试用 Swift 重写部分 ObjC 代码,Swift 中有很多 cocoa 对象属性 return 可选值。有没有更好的模式而不是这种深度嵌套:

  if let panel = self.window {
     if let screens = NSScreen.screens() {
        if  let screenRect = screens[0].frame {
           let statusRect = CGRectZero
              ...

你可以像这样强制解包:

NSScreen.screens()!.first!.frame!
NSScreen.screens()!.first!.frame!.width
NSScreen.screens()!.first!.frame!.height


// safely unwraping it as required an mentioned by Gregory
if let frame = NSScreen.screens()?.first?.frame {
    println(frame)
}
if let width = NSScreen.screens()?.first?.frame?.width {
    println(width)
}
if let height = NSScreen.screens()?.first?.frame?.height {
    println(height)
}

虽然 Leonardo 的回答很好,但它可能会导致异常,除非你知道对象是非可选的,更好的模式是这样的,它应该被视为伪代码:

if let frame = NSScreen.screens()?.first?.frame {
   // Do something with frame.
}

换句话说,使用可选链接 (?.),但仅在链的最后部分使用 let

如果你想链接不同类型的可选值,你也可以创建这样的运算符,其中只返回最后一个的值:

infix operator ??? { associativity left }

func ??? <T1, T2>(t1: T1?, t2: @autoclosure () -> T2?) -> T2? {
    return t1 == nil ? nil : t2()
}

if let frame = self.window ??? NSScreen.screens()?.first?.frame {
    // Do something with frame
}

这当然是受到了Haskell的bind的启发。但尽管这很有趣,我不推荐它。我认为它很清楚。 (顺便说一下,????? 之间的区别是前者 要求 lhs 和 rhs 是相同的类型(或超类型) ,而后者确实如此。??? 总是 returns 它的 rhs 或 nil。)

wrote a blog post 不久前探索了几种不同的方法来解包多个可选值。

您可以创建一个一次性解包多个可选值的函数:

func if_let<T, U, V>(a: Optional<T>, b: Optional<U>,
  c: Optional<V>, fn: (T, U, V) -> ()) {
  if let a = a {
    if let b = b {
      if let c = c {
        fn(a, b, c)
      }
    }
  }
}

用法如下:

if_let(a, b, c) {
  a, b, c in
  println("\(a) - \(b) - \(c)")
}

有人还提出了一个使用元组的巧妙解决方案,可以是 found in this gist

从 Swift 1.2 开始,您可以在一行中展开,如以下答案所述:

unwrapping multiple optionals in if statement

请在此处阅读有关可选链接的信息: Linking Multiple Levels of Chaining

你可以玩这个游乐场看看它是如何工作的:

import UIKit


class AAA {
    var string: String? = nil
}

class AA {
    var aaa: [AAA?]? = nil
}

class A {
    var aa: AA? = nil
}


var a: A?

a = A()

/// Comment/Uncomment next lines
a?.aa = AA()
//a?.aa?.aaa = [AAA()]
a?.aa?.aaa = []
a?.aa?.aaa?.first??.string = "My String Value"

if let myString = a?.aa?.aaa?.first??.string {
    print("myString: \(myString)")
} else {
    print("myString does not acceptable")
}

您的代码可能如下所示:

if let panel = self.window, let statusRect = NSScreen.screens()?.first??.frame ?? CGRect.zero {
    /// your code to work with panel and statusRect
}