为啥隐式展开的可选选项不在 [String : Any] 类型的字典中展开

Posted

技术标签:

【中文标题】为啥隐式展开的可选选项不在 [String : Any] 类型的字典中展开【英文标题】:Why does implicitly unwrapped optional not unwrap in dictionary of type [String : Any]为什么隐式展开的可选选项不在 [String : Any] 类型的字典中展开 【发布时间】:2018-04-02 10:27:13 【问题描述】:

如果我在我的类中声明了一个隐式展开的可选选项,然后我在 [String : Any] 类型的 Dictionary 中引用它,它不会被展开。为什么是这样?为什么Any,不是可选的,不强制解包?

var aString: String! = "hello"
var params : [String : Any] = [
    "myString" : aString
]
print(params)
// This prints ["myString": Swift.ImplicitlyUnwrappedOptional<Swift.String>.some("hello")]

请注意,如果我将字典指定为 [String : String] 类型,它将被解包,但这对于我的 Dictionary 中需要多个类型时没有用。

【问题讨论】:

字典中不能有不同的类型。根据定义 - 所有键必须属于同一类型,所有值必须属于同一类型。 @MartinMuldoon 这不是真的,请参阅:developer.apple.com/documentation/swift/dictionary 确实如此。所有密钥必须是同一类型。所有值必须属于同一类型。键的类型不必与值的类型相同。所以你可以有 [String : Int] 例如。 请先在 Playground 中尝试一下再发表评论。我可以在同一个字典中指定StringInt 的键和值作为[AnyHashable : Any] 类型的异构集合 顺便说一句,字典正在打印["myString": hello]。没有这样的错误或警告。 See this 【参考方案1】:

根据SE-0054 规定的规则,IUO 仅在需要其展开类型的上下文中强制展开。在你的情况下,IUO 不需要被强制解包以被强制为Any(因为Any 可以代表任何值),所以它不是。

在这些问答中更详细地讨论了这种行为:

Swift 3 incorrect string interpolation with implicitly unwrapped Optionals Implicitly unwrapped optional assign in Xcode 8

您的字典中最终得到 ImplicitlyUnwrappedOptional 值的事实是已在最新的 Swift 快照中删除的遗留行为,将来您将最终得到 Optional 值(因为 IUO 不是更长的类型)。

这里需要注意的重要一点(我肯定会绊倒人们)是 IUO 的打印在 4.1 中发生了变化。

在 Swift 4.0.3 中,您的示例打印如下:

var aString: String! = "hello"
var params : [String : Any] = [
  "myString" : aString
]
print(params)
// This prints ["myString": hello]

给你一个错觉,当被强制到Any 时,IUO 被强制解开。然而,这正是 Swift 4.0.3 中 IUO 的打印方式——如果它们有一个值,那么它们将打印为该值,否则它们将打印为 nil

var aString: String! = nil
var params : [String : Any] = [
  "myString" : aString
]
print(params)
// This prints ["myString": nil]

在 Swift 4.1 中发生这种变化的原因是 ImplicitlyUnwrappedOptionalCustom(Debug)StringConvertible 的一致性被删除 in this commit 以便在删除类型本身方面取得进展。所以现在ImplicitlyUnwrappedOptional 值使用 Swift 的默认打印机制(使用反射)打印。

因此,在字典中,您会得到 IUO 的默认 debugDescription,如下所示:

let aString: String! = "hello"
let params : [String : Any] = [
  "myString" : aString
]
print(params)
// This prints ["myString": Swift.ImplicitlyUnwrappedOptional<Swift.String>.some("hello")]

如果你自己打印它,你会得到它的默认description,看起来像这样:

let aString: String! = "hello"
print(aString) // some("hello")

这是因为在 Swift 4.1 中,ImplicitlyUnwrappedOptional 类型的实现方式与 Optional 相同,枚举有两种情况:

public enum ImplicitlyUnwrappedOptional<Wrapped> : ExpressibleByNilLiteral 
  // The compiler has special knowledge of the existence of
  // `ImplicitlyUnwrappedOptional<Wrapped>`, but always interacts with it using
  // the library intrinsics below.

  /// The absence of a value. Typically written using the nil literal, `nil`.
  case none

  /// The presence of a value, stored as `Wrapped`.
  case some(Wrapped)

  // ...

对于具有有效负载值的 IUO,Swift 的默认反射将因此将其打印为包含包装值的some

但这只是暂时的; IUO 类型目前(在 Swift 4.1 中)已弃用,但它将在 Swift 4.2 中删除。编译器内部在很多地方都使用了 IUO 类型,其中使用了quite a bit of work to remove。因此,在 4.2 中,您的字典中有实际 Optional 值,其打印结果类似于Optional("hello")

【讨论】:

【参考方案2】:

改变这一行:

var params : [String : Any] = ["myString" : aString]

到:

var params : [String : String] = ["myString" : aString]

【讨论】:

我已经在我的帖子中提到了这一点,但这对于接受多种类型的Dictionary 的用例没有帮助 因为 Any 可以是任何东西...... Int、Double、Class,所以它不知道你想要什么。您显然希望它是 String 类型,因此请以这种方式进行转换。 [字符串:字符串] 是的,但是 Int、Double、Class 都是未包装的类型。我原以为这意味着 IUO 将被打开…… OK.. 好问题。从字典中检索值时,它始终是可选的。你必须打开它。原因是 Dictionary 可能为空。在您尝试访问该值之前,编译器不会知道。 这有意义吗?【参考方案3】:

当你定义一个字典类型时

let dictionary = [String:Any]()

你可以在这本字典里放任何东西

喜欢

 dictionary["name"] = "xyz"

 dictionary["code"] = 123

let dictionary = [String:String]()

你只能放一个字符串值

这就是为什么在访问值时必须解开值,因为它可以是任何东西

【讨论】:

【参考方案4】: Any 可以表示任何类型的实例,包括函数类型和可选类型。 AnyObject 可以表示任何类类型的实例。

您的aString 是一个实例对象,不应用于在属性Area 中声明,如果您将该行放在任何函数/实例function() 中,那么它将完美运行,因为它将是您的实例类型.

一句话,Swift的属性区不能声明实例类型。

override func viewDidLoad() 
   let params : [String : Any] = ["myString": aString]

即使在属性区,你也不能这样做:

var aString: String! = "String"
var abc = aString

你必须abc = aString 做任何Method() 才能工作。

希望对你有所帮助。

【讨论】:

如果字典在函数外声明,那么它如何打印? @TheTiger 好吧,字典由 Key Value Compliant 组成,在 swift 中,每件事都在任何函数下执行,如果你想在属性区域声明字典意味着在函数之外和类内,那么你必须为键和值提供字符串像这样:var testDic : [String : Array&lt;String&gt;] = ["someStringKey" : ["1", "2"]]var testDic1: Dictionary = ["someStringKey" : ["1", "2"]] 我是说字典可能在任何函数体中声明,这就是为什么 OP 能够打印 params 否则它甚至无法编译。 我按照我的方式做了同样的事情,找到了预期的结果,我不知道他是怎么得到那种结果的。 @TheTiger 在哪里,您不能在属性区域中声明像问题一样。

以上是关于为啥隐式展开的可选选项不在 [String : Any] 类型的字典中展开的主要内容,如果未能解决你的问题,请参考以下文章

访问无价值的隐式解包可选?

为啥 Swift 隐式展开可选的“nil”?

什么是解包变量?

隐式展开的可选项和非可选项之间的区别

在 Apple Swift 中,在啥情况下我不想要一个隐式展开的可选项?

可选类型的值必须被解包以引用成员,但它是隐式解包的可选属性