为什么 nil 合并运算符 ?? 返回 nil?

5
考虑以下涉及 nil 合并操作符 ?? 的示例:
let mysteryInc = ["Fred": "Jones", "Scooby-Doo": nil]

let lastname = mysteryInc["Scooby-Doo"] ?? "no last name"

print(lastname == nil)  // true

如最后一个print语句所示,nil coalescing operator的结果是nil
如果nil coalescing operator应该展开一个Optional,为什么会返回nil

2
还有一个有趣的用例:mysteryInc["Scooby-Doo"] = nil。它会做什么? - Sulthan
2
@Sulthan,mysteryInc["Fred"] = nil会从字典中完全删除FredmysteryInc["Fred"] = Optional(nil)将“Jones”替换为nil - vacawama
1个回答

10

为了了解这里发生了什么,请将字典查找分配给一个常量:

let name = mysteryInc["Scooby-Doo"]
print(type(of: name))

输出:

Optional<Optional<String>>
所以,name 是一个双重 Optional,即 String??
当使用空值合并运算符时,它会展开外部的 Optional,并留下一个 Optional<String>(也就是 String?)。在问题示例中,Swift 将字符串字面量 "no last name" 视为 String? 类型,以便应用 ??
如果检查 name 的值,您会发现它是 Optional(nil)。所有字典查找都会返回 Optionals,因为 key 可能不存在(在这种情况下,它们会返回 nil)。在本例中,mysteryInc 字典的类型为 [String: String?]。对应于 "Scooby-Doo" 的值为 nil,然后被包装成 Optional(由于字典查找)变成了 Optional(nil)
最后,?? 解包了值 Optional(nil)。由于 Optional(nil) != nil,Swift 解开了 Optional(nil) 并返回 nil 而不是预期的 "no last name"
这就是如何通过空值合并操作符获得 nil。它确实展开了 Optional,只是这个 Optional 中恰好包含了一个 nil
如 @LeoDabus 在评论中指出的那样,处理这种情况的正确方法是在应用空值合并运算符之前将 name 有条件地转换为 String
let lastname = mysteryInc["Scooby-Doo"] as? String ?? "no last name"

实际上,最好避免将可选项(Optionals)作为字典的值,正如@Sulthan提到的那样:

mysteryInc["Fred"] = nil是什么意思?

nil分配给字典条目会将该条目从字典中删除,因此mysteryInc["Fred"] = nil不会用nil替换Optional("Jones"),而是完全删除了"Fred" 条目。要将"Fred"保留在字典中并使其姓氏为nil,需要分配mysteryInc["Fred"] = Optional(nil)mysteryInc.updateValue(nil, forKey: "Fred")


2
你需要先将其转换为字符串,然后使用空合运算符 as? String ?? "no last name" - Leo Dabus
@LeoDabus,感谢您的建议。有趣的是,这只在Swift 3.1之后才起作用。在Swift 3.02及更早版本中,它会出现以下错误:Error: downcast from 'String??' to 'String' only unwraps optionals; did you mean to use '!!'? - vacawama
不用谢。关于之前的语法,我不清楚。我从来不会在字典中使用可选值类型,因此以前从未尝试过。通常我会使用结构体。 - Leo Dabus
顺便说一下,有趣的是 Optional(nil) 的东西。 - Leo Dabus
1
我可能更喜欢直接使用.updateValue方法,而不是分配dict[key] = Optional(nil) - Sulthan
1
是的。我通常也避免在字典中使用可选值。但在这个例子中,它确实有用。mysteryInc["Scooby-Doo"] == Optional(nil)true,意味着 Scooby-DoomysteryInc 中,但没有姓氏。mysteryInc["Speed Buggy"] == nil 是 true,意味着 Speed Buggy 不在 mysteryInc 中。 - vacawama

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