【问题标题】:How to properly check if non-Optional return value is valid?如何正确检查非可选返回值是否有效?
【发布时间】:2014-06-26 20:37:14
【问题描述】:

我在尝试检查返回值时遇到了一个奇怪的情况,我想知道如何在 Swift 意义上“正确”执行此操作。

我有一个NSStatusItem(名为item),我正在尝试将NSStatusItem 分配给NSImage。当我创建NSImage 时,因为我将图像名称的字符串值传递给它,所以我想确保NSImage 实际上是有效的(如果我输入错误的图像名称字符串怎么办?)。

我尝试的第一件事是:

if let image: NSImage? = NSImage(named: "CorrectIconName") {
    item.image = image
}

但这会产生错误“条件绑定中的绑定值必须是可选类型”。我以为image: NSImage? 说得很清楚它是可选的,但我想不是。

我把它改成了这样:

let image: NSImage? = NSImage(named: "CorrectIconName")
if image {
     item.image = image
}

这完全没问题。但我不明白为什么会这样,而第一个例子没有。似乎或多或少是完全相同的东西。而且由于第一个没有编译,我想我会尝试一些其他的路线......

由于NSImage(named:) 确实返回NSImage 而不是NSImage?,我想如果我将构造函数的返回值直接分配给item 会发生什么:

item.image = NSImage(named: "CorrectIconName")

这可行,但不允许进行我想做的错误检查。如果字符串错误,NSStatusItem 会为图像获取nil,这导致我有一个不可见的状态栏项目。

接下来,我尝试了这个:

let image: NSImage = NSImage(named: "CorrectIconName")
if image {
    item.image = image
}

但这给出了错误“类型'NSImage'不确认协议'LogicValue'”,我猜这意味着你不能使用if语句检查它是否是nil

但是,您可以通过执行以下操作来检查它是否为 nil:

let image: NSImage = NSImage(named: "CorrectIconName")
if image != nil {
    item.image = image
}

那么,问题来了:如果返回值不是可选的,应该如何检查返回值?

【问题讨论】:

  • 好问题,但我认为这里真正的问题是:为什么init(name:) 返回NSImage?我相信答案很简单:“它是自动 ObjC -> Swift 标头生成器的错误,请提交错误报告!”
  • @JackWu,这种行为也可能是一个错误。不过我不确定!

标签: swift


【解决方案1】:

它实际上可选的,编译器只是没有显示给你。

在 Apple 关于使用 Objective-C 对象的文档中,它说从 Objective-C API 导入的所有对象实际上都是隐式展开的可选项(就像我们使用 ! 手动声明一样):

在某些情况下,您可能完全可以肯定 Objective-C 方法或属性永远不会返回 nil 对象引用。做 这种特殊场景中的对象使用起来更方便,Swift 将对象类型导入为隐式展开的选项。隐式 展开的可选类型包括所有的安全特性 可选类型。此外,您可以直接访问该值,而无需 检查nil 或自己解包。 [source]

不幸的是,编译器/语法检查器不会这样对待它们。因此,正确的检查方法是将image 声明为NSImage 初始化程序实际返回的类型,一个隐式展开的可选NSImage

let image: NSImage! = NSImage(named: "CorrectIconName")
if image {
    // do something
}

替代方法(通过@vacawama):

if let image = NSImage(named: "CorrectIconName") as NSImage! {
    // do something
}

【讨论】:

  • 内特是正确的。你也可以这样做: if let image = NSImage(named: "CorrectIconName") as NSImage! { item.image = image }
  • 所以当文档谈论“Objective-C 方法”或“Objective-C 对象”时,它也是在谈论 Cocoa/Cocoa Touch 方法/对象? (我假设 Cocoa/Cocoa Touch 是 Swift 化的,并且使用它不是“与 Objective-C API 交互”。)此外,编译器/语法检查器不会将这些对象视为隐式展开的选项应该提交的错误? API 说它返回NSImage,而不是NSImage?NSImage!
  • 是的 - 它表示在 Objective-C API 中定义的方法和对象。问题是 Objective-C 中的所有对象都可以是nil,这与 Swift 不同。似乎 Swift 中内置的许多 Optional 功能专门用于弥合两种语言之间的鸿沟。
【解决方案2】:

可能会返回 nil 的 Objective-C 初始化程序与 Swift 的 init 语义之间存在一些不匹配。如果您调用 SomeObject(...) 它由语言定义以创建 SomeObject NOT SomeObject? 的实例

在 Cocoa/Foundation 框架中有几个初始化器示例,从 Swift 的角度来看,它们生成 NSSomething? 而不是隐含的 NSSomething

Apple 有多种方法可以在不影响 Swift 类型系统的情况下解决这个问题,但在他们这样做之前,您可以通过多种方式“做正确的事”。

在条件中重新包装对象 - 这可行但很笨重:

var maybeImage: NSImage? = NSImage(named: "CorrectIconName")
if let image = maybeImage {

}

更好的办法是制作自己的加载 NSImage 的函数

func loadImageNamed(name: String) -> NSImage? {
    return NSImage(named: name)
}

func doSomethingWithAnImage() {
    if let image = loadImageNamed( "CorrectIconName") {
        ....
    }
}

您也可以扩展 NSImage。

extension NSImage {
    class func imageWithName(name: String) -> NSImage? {
        return NSImage(named: name)
    }
}

现在你可以做自然的事情了。

if let image = NSImage.imageWithName("CorrectIconName") {
}

这对于 Swift/Cocoa API 来说似乎是正确的事情,我希望 Apple 在未来做类似的事情。

【讨论】:

    【解决方案3】:

    除非我遗漏了一些完全明显的东西,否则这更多的是见仁见智。您可能应该将返回值存储在一个变量中,然后检查该变量的内容。我认为这主要是因为如果返回是可选的,如果该变量最终没有被声明怎么办? if 语句很合适!

    这与您问题的第一部分有关,这似乎也与您问题的其余部分有关,但我可能会感到困惑...

    【讨论】:

      【解决方案4】:

      我在 IOS 中遇到了类似的问题,并使用 ?-operator 解决了它:

      if let im = UIImage(named: "Meetup.png")? {
          imageLayer.contents = im.CGImage
      }
      

      没有 ?它不会进入 if 分支,但是当使用 ?是的!

      【讨论】:

        猜你喜欢
        • 2018-06-07
        • 1970-01-01
        • 2011-05-27
        • 1970-01-01
        • 2014-11-24
        • 2019-05-07
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多