将强制向下转换视为可选项永远不会产生'nil'

40

我一直在尝试使用Swift,发现当将一个对象向下转型以插入到字典中时,会出现奇怪的警告:Treating a forced downcast to 'String' as optional will never produce 'nil'。如果我用as替换为as?,那么警告就会消失。

func test() -> AnyObject! {
  return "Hi!"
}

var dict = Dictionary<String,String>()
dict["test"]=test() as String

苹果的文档如下:

由于向下转换可能失败,类型转换操作符有两种不同形式。可选形式 as? 返回您尝试向下转换到的类型的可选值。强制形式 as 尝试向下转换并将结果作为单个复合操作强制解包。

我不清楚为什么在这里使用 as? 而不是 as 是正确的。一些测试表明,如果我将 test() 改为返回 Int 而不是 String,则如果我继续使用 as,代码将退出并显示错误。如果我切换到使用 as?,则代码将继续正常执行并跳过该语句(dict 将保持为空)。但是,我不确定为什么这样更好。在我看来,我宁愿程序因出现错误而退出,并让我知道强制转换失败,而不是忽略错误的语句并继续执行。

根据文档,我应该仅在“确信向下转换始终成功时”使用强制形式。在这种情况下,我确信向下转换始终成功,因为我知道 test() 只能返回一个字符串,所以我会认为这是向下转换的完美情况。那么为什么编译器会给我一个警告呢?


1
首先,有很多Cocoa类型的桥接,因为如果没有import Foundation,它甚至无法编译,因为String不是一个类类型,也不兼容AnyObject。如果你将最后一个String改成NSString,警告就会消失,这可能意味着你在as中使用了非类类型的String,导致了问题。 - newacct
有趣的是,如果你在字典定义或者向下转型中将String替换为NSString,警告就会消失。然而,如果你在两个位置都将String替换为NSString,警告仍然存在。 - Pamelloes
注意:自从Swift 2以来,许多as的功能已被删除,并被as!所取代。因此,当您使用as!时,您将会得到相同的错误(使用as将不再生成此错误)。 - mfaani
3个回答

46

让我们仔细看一下你的最后一行,并逐步展开它以查看发生了什么:

let temporaryAnyObject = test()
let temporaryString = temporaryAnyObject as String
dict["test"] = temporaryString

错误出现在第二行,您要求编译器强制将 temporaryAnyObject 定义为 String。代码仍将编译(假设您不将警告视为错误),但如果 temporaryAnyObject 实际上不是 String,则会崩溃。

没有使用 ?as 的工作方式基本上是说“从现在开始,如果结果确实是那种类型,则将此表达式的结果作为该类型处理,否则我们将变得不一致,无法继续运行。

有了 ?as? 的工作方式是说“从现在开始,如果结果确实是那种类型,则将此表达式的结果作为该类型处理,否则此表达式的结果为 nil

因此,在上面的示例中,如果 test() 确实返回 String,则 as 向下转换成功,temporaryString 现在是一个 String。如果 test() 没有返回 String,而是返回 Int 或任何其他未从 String 继承的东西,则 as 将失败,代码将无法继续运行。

这是因为作为完全控制的开发人员,您告诉系统以这种方式行事,即不放置可选的 ? 指示器。 as 命令特别意味着您不容忍可选行为,并且要求执行该向下转换。

如果您放置了 ?,那么 temporaryString 将为 nil,第三行将简单地从字典中删除“test”键值对。

这可能看起来很奇怪,但这仅因为许多语言(如 Obj-C)的默认行为相反,默认情况下将所有内容视为可选,并依赖于您放置自己的检查和断言。

编辑 - Swift 2 更新

自 Swift 2 以来,强制、可失败向下转换运算符 as 已被取消,并被更具 Swift 风格的 as! 取代。行为相同。


8
我了解你关于 "as" 和 "as?" 的说法,但我仍然不明白为什么会出现警告。另外,如果我按照你所概述的方式将我的代码分成三行,则警告实际上会消失。只有当语句在一行中出现时才会出现警告。 - Pamelloes
1
啊,你说得对。嗯,实际上这是因为我在代码中并没有出错。正确的代码应该是:let temporaryAnyObject: AnyObject = test(); let temporaryString: String? = temporaryAnyObject as String; dict["test"] = temporaryString 你会发现,这段代码也会产生同样的警告。 - Ryan
原因是 dict["test"] = * 希望得到一个 String?,所以我刚才发布的更改明确地将其类型为 String?,而不是 String!。把 "as" 看作一种函数。"as" 函数返回一个 String!,而 "as?" 返回一个 String?。 - Ryan
啊!我明白了。我错过了字典需要一个字符串的事实?谢谢! - Pamelloes
1
这源于一个事实,即您可以通过调用 dict["apples"] = nil 来删除一个键(比方说“苹果”)。 - Ryan
2
随着昨天Swift编译器的更新,警告现在提供了一个解决方案,即将向下转换放在括号中,而不是用可选向下转换替换它。 - Pamelloes

6

您可以从两个角度解决此警告。1.您返回的值 2.您 期望 返回的类型。另一个答案提到了第一个方面。我在谈论第二个方面。

这是因为您正在返回一个强制展开和转换可选项 的值。编译器会说:“如果您真的想强制转换所有可选项,为什么不将期望的返回参数设置为非可选项呢?”

例如,如果您编写了以下内容:

func returnSomething<T> -> T?{ // I'm an optional, I can handle nils SAFELY and won't crash.

return UIViewController as! T // will never return a safe nil, will just CRASH

}

基本上你告诉自己(和编译器)我想安全处理nil,但是在下一行中,你说不要!!!

编译器会发出警告:

将强制向下转换为'T'视为可选项将永远不会产生'nil'

另一种选择是删除?

func returnSomething<T>() -> T{ // I can't handle nils

    return UIViewController() as! T // I will crash on nils
}

话虽如此,最好的方法可能是不使用强制转换,而是这样做:

func returnSomething<T>() -> T?{ // I can handle nils

    return UIViewController() as? T // I won't crash on nils
}

不强制转换对我有用(例如 as? ),谢谢! - Andi

0

看起来已经有关于这个警告的漏洞存在了几年了... https://bugs.swift.org/browse/SR-4209 所以即使在明显知道你正在做什么并且正确的情况下,它也会显示出来。


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