解包多个Swift可选项

3

我正在尝试用Swift重写ObjC代码的部分内容,许多Cocoa对象属性在Swift中返回可选值。是否有更好的模式替代这种深层嵌套:

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

可能是Swift元组的可选绑定的重复问题。 - Alex Brown
请参见 https://dev59.com/iGAf5IYBdhLWcg3wgzHI#25048307,该链接与编程有关。 - Alex Brown
1
@AlexBrown 抱歉,我认为这两个问题都不完全相同。提问者谈论的是可选链。请查看下面的一些答案。(我不怀疑,这个问题可能是其他问题的重复,只是不是你链接的那些问题。) - Gregory Higley
是的,我之前看到过这些辅助方法,但是这些辅助方法的问题在于它们返回固定长度的元组。所以,根据嵌套的深度,我们需要多个这样的方法来处理更深层次的嵌套。 - Sinisa Drpa
我不知道你为什么说这是一系列可选项 - 证据不明确 - 第一行与第二行和第三行不构成链条的一部分。 - Alex Brown
1
这绝对是一个重复的问题:https://dev59.com/il8d5IYBdhLWcg3wkS7Y#26673847 - Alex Brown
5个回答

2

虽然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绑定的启发。但尽管有趣,我不建议这样做。我认为这很难懂。(顺便说一下,?????之间的区别是前者不需要lhs和rhs是相同类型(或更高类型),而后者则需要。 ???始终返回其rhs或nil。)


@AlexBrown上面的链接提供了很好的观点和详细的解释,但这个答案是最优雅的。唯一的缺点是你不知道哪一步失败了。 - Sinisa Drpa
如果您关心哪些步骤失败了,请不要使用此方法。相反,使用 ?.if let 的组合。 - Gregory Higley
1
完成了代码移植,但是我发现解包多个Swift可选项很烦人,而且阅读起来很难看。有时候,例如:if let statusItemView = statusItem?.view as? StatusItemView 当你需要强制转换时,你无法继续链式调用,只能嵌套使用。许多Cocoa属性被重写为可选值,这看起来像是一种临时解决方案,以确保避免nil并保持与ObjC的兼容性。在我看来,当与当前的cocoa框架一起使用时,Swift代码的可读性并不好。 - Sinisa Drpa
@SinisaDrpa 我同意。这是从弱类型动态语言转向强类型静态语言的产物。 - Gregory Higley

1

一段时间前,我写了一个博客文章,探讨了几种不同的方法来解包多个可选项。

您可以创建一个函数,在一个步骤中解包多个可选项:

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)")
}

有人也提出了一个使用元组的简洁解决方案,可以在这个代码片段中找到。


我之前找到了它,非常好的帖子。唯一的缺点就是像你说的那样不能使用不同数量的参数。对于2、3...个嵌套可选项,我们需要相同函数的多个版本。 - Sinisa Drpa
@SinisaDrpa非常正确,对于我所见过的所有其他实现此目标的技术都适用。我们只能等待并希望有更好的语言支持! - ColinE

1
你可以像这样强制解包它:

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)
}

1
如果其中一个值为nil,那么它是不安全的,会导致崩溃。 - Sinisa Drpa

0

0
请阅读关于可选链的内容: 链接多级链接 您可以使用这个Playground来查看它是如何工作的:
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
}

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