为啥 Swift 零合并三元运算符不返回未包装类型?

Posted

技术标签:

【中文标题】为啥 Swift 零合并三元运算符不返回未包装类型?【英文标题】:Why doesn't Swift nil-coalescing ternary operator return unwrapped type?为什么 Swift 零合并三元运算符不返回未包装类型? 【发布时间】:2015-03-31 03:45:52 【问题描述】:

我读到三元运算符 ?? 如果它不是 nil 则解开一个可选项,但如果我这样做:

var type: String?
type = "milk"
let certainType = type ?? "melon"

那么某些类型仍然是String?,如果我这样做了

println("it's a \(certainType)")

它会打印出来:

it's a Optional("milk")

想法?

更新:

抱歉,我的意思是 var type: String?

我知道它应该打印“它是牛奶”,但我在控制台中看到的是“它是可选的(“牛奶”)”——其他人遇到过同样的问题吗?会不会是字符串插值造成的?

@Antonio 询问,这里有更多上下文和真实代码以及日志记录快照 - 类型来自 Note,这是一个用于处理 xcdatamodel 的 NSManagedObject 类

class Note: NSManagedObject 
  @NSManaged var type: String?

我在某个时候将类型设置为“待办事项”,然后使用以下代码将它们打印出来:

println("type class:\(_stdlib_getDemangledTypeName(note.type))")
let type1 = note.type ?? "note"
println("type1:\(type1)")
let type2: String = note.type ?? "note"
println("type2:\(type2)")

还有输出:

type class:Swift.Optional
type1:Optional("todo")
type2:todo

如您所见,如果我没有将 type1 的类型显式标记为字符串,它将打印不想要的结果 Optional("todo") - 我使用字符串插值中的类型来构造路径,所以这很重要

【问题讨论】:

我认为您在粘贴代码时犯了一个错误 - 它无法编译,因为不可变对象必须在声明它的同一行中初始化。但是在修复它let type: String? = "milk" 之后,它会打印正确的结果"it's a milk" 如果能看到实际的编译代码就好了…… 我猜提问者正在查看的真实代码(与他们输入的非编译代码相反)是typeString??”melon” 的情况实际上是String? 类型的变量。在任何一种情况下,String?? ?? StringString? ?? String? 都计算为String?(尽管出于不同的原因)。 String ?? String? 也是。 @Antonio 感谢编辑 从未来开始:现在是 2016 年年中,我将这里的前 3 行粘贴到“xcrun swift”中,它工作正常(输出中没有“可选”),所以我猜这是已修复的错误。 【参考方案1】:

我无法证明我要说什么,所以非常感谢任何反馈

OP 断言该代码类似于:

var type: String? = "milk"
let certainType = type ?? "melon"
println("it's a \(certainType)")

打印一个意外的字符串:

"这是一个可选的("milk")"

应该是这样的:

“这是牛奶”

事实证明,当变量实际上是具有@NSManaged 属性的属性时,就会发生这种情况。

我怀疑类型推断存在错误。 OP声明:

let certainType = type ?? "melon"

打印错误的结果,而:

let certainType: String = type ?? "melon"

打印正确的。

所以由于某种原因,在没有明确指出变量类型的情况下,nil 合并运算符返回一个可选的

如果我将type 变量的类型更改为AnyObjectAnyObject?,它实际上会打印unexpected 结果

var type: AnyObject = "milk"
let certainType = type ?? "melon"
println("it's a \(certainType)")

“这是一个可选的(牛奶)”

我的猜测是:因为使用了@NSManaged 属性,所以在使用时会推断出错误的类型(AnyObject?),除非明确指出正确的类型

至于为什么会这样,不知道(除了认为这是一个错误)

请随意对这个答案投赞成票或反对票,最重要的是不要将其视为解决方案 - 不过我会很感激反馈,因为我很想知道发生了什么以及它是否真的是一个错误。

【讨论】:

感谢您的发现!我觉得这是对这个问题的正确解释——它可能是一个 @NSManaged 错误,或者我没有正确使用它(可能应该使用 AnyObject?而不是 String?暂时的属性,这将是一个蹩脚的实现Swift 中的 NSManaged 的​​东西)。无论哪种方式,这都解释了发生奇怪情况的情况。感谢其他人的更多想法。【参考方案2】:

这是(我认为)设计的。如果你这样做:

let type = "mil"
println("it's a \(type)")

如果我没记错的话,这将打印it's a "milk",这在调试时比it's a String 有用得多。请注意,\(...) 类似于 Objective-C 中的 %@:类可以覆盖其字符串表示。

此外,正如安东尼奥已经指出的那样:

    您不能更改不可变变量(即let)。 因此可选的 let 变量是无用的(即不要使用 let type: String? = "milk",因为它显然永远不会为零)。

【讨论】:

抱歉编辑了!它打印出“它是可选的(“牛奶”)”而不是“它是牛奶”对我来说:(【参考方案3】:

我读到三元运算符 ??如果它不是 nil,则解开一个可选项,但是...

它确实打开了它。

让类型:字符串? 类型=“牛奶” 让某些类型 = 类型 ?? “瓜”

您的代码无法编译,但如果您将 let 更改为 var,它将起作用,并且 certainType 将是 String 类型。

var type: String?
type = "milk"
let certainType = type ?? "melon"

println("it's a \(certainType)") // prints "it's a milk"

【讨论】:

抱歉编辑了!它打印出“它是可选的(“牛奶”)”而不是“它是牛奶”对我来说 :( – hyouuu just now edit【参考方案4】:

这被称为Nil Coalescing Operator。

如果您有可选的a,如果a 不是nilb,则a ?? b 的结果将为a!。这是这个表达式的简写:

a != nil ? a! : b

因此,您正确地使用了该运算符,但您必须在声明它的同一行上初始化一个常量:

let type: String? = "milk"

但是,我假设您不希望它在这种情况下成为常量(因为您正在检查它是什么),因此如果您使用 var 声明它,您现有的代码应该可以工作.

【讨论】:

【参考方案5】:

我遇到了类似的问题。

我有一个[String : AnyObject] 类型的字典,但我所追求的条目是String 值。担心它可能并不总是存在。我正在使用以下代码检索它:

let message = dictionary["message"] ?? "No message"

但是,当条目存在时,仍然会给出字符串:

可选(实际消息)

即变量message的类型为String?

我改成:

let message = (dictionary["message"] as? String) ?? "No message"

...现在检索到的值是一个未包装的非可选字符串。

【讨论】:

【参考方案6】:

这就是你想要的

let certainType = type! ?? "melon"

注意我打开了类型。它适用于您:

let type2: String = note.type ?? "note"

因为您实际上是在将转换为 String 的内容展开。

【讨论】:

【参考方案7】:

我的问题是 @NSManaged 类型的 NSNumber? 属性,我试图将其包装在一个字符串中。

let time = "\(nsManagedObject.optionalNsNumber ?? "") months" /// result "Optional(<random number>) months"

解决此问题的方法是将NSNumber 转换为stringValue。下面是按预期工作的代码。

let time = "\(nsManagedObject.optionalNsNumber.stringValue ?? "") months" /// result "<random number> months"

如果值包含在字符串中,Swift 编译器将不会发现问题。如果您尝试使用nil coalescing 运算符,然后将结果添加到字符串中,它将指向手头的错误Can't add NSObject and String

@NSManaged 属性包装成字符串时要小心。

【讨论】:

【参考方案8】:

我找到了一种方法试试看。

infix operator ???


/// use this custom operator to overcome issue related to '??' which always needs assignment to remove Optional
///
/// - parameter lhs: left hand side variable
/// - parameter rhs: right hand side variable
///
/// - returns: it will return unwrapped value of one of the variable which has not nil value
func ???(lhs: Any?, rhs: Any?) -> Any 

    let temp : Any = lhs ?? rhs

    return temp


【讨论】:

以上是关于为啥 Swift 零合并三元运算符不返回未包装类型?的主要内容,如果未能解决你的问题,请参考以下文章

为啥带赋值的三元运算符不返回预期的输出?

为啥不能重载三元运算符?

三元运算符为啥以及何时返回左值?

三元运算符和包装类的一些细节(面试题)

为空的非零字符串合并 Swift 运算符

为啥在三元运算符中使用“0”会返回第一个值?