为啥 Swift4 会强制转换 UIButton 数组!到 [UIButton?] 类型?

Posted

技术标签:

【中文标题】为啥 Swift4 会强制转换 UIButton 数组!到 [UIButton?] 类型?【英文标题】:Why does Swift4 cast an array of UIButton! to [UIButton?] type?为什么 Swift4 会强制转换 UIButton 数组!到 [UIButton?] 类型? 【发布时间】:2018-01-03 16:31:43 【问题描述】:

我今天遇到了一个奇怪的问题。请看这段代码:

class A 

    var button1: UIButton!
    var button2: UIButton!

    func foo() 
        let array = [button1, button2]
    

Xcode 说 array[UIButton?] 类型。出于某种原因,Swift4 将 UIButton! 元素转换为 UIButton?。为什么?

【问题讨论】:

因为button1button2 是可选的。 Swift 无法创建[UIButton!] 数组吗? 你为什么用!强制解包?您是否正在尝试创建一组非可选 UIButtons?通常,使用! 解包仅在类初始化时对象为 nil 时使用,但稍后肯定会初始化。 @KamilHarasimowicz 你不能有[UIButton!] - 根据SE-0054,T! 不再是一个独特的类型;它现在与T? 的类型相同,但它允许将属性附加到声明(例如属性),从而允许在某些情况下强制解包。比较***.com/q/38849549/2976878、***.com/q/39537177/2976878、***.com/q/39633481/2976878 & @Stephen 假设button1button2 是网点,foo() 函数将自定义它们。 【参考方案1】:

解释

ImplicitlyUnwrappedOptional 不是一个独特的类型,而是一个普通的Optional,其属性声明其值可能是隐式强制的(基于SE-0054):

但是,外观!在属性或变量声明的类型的末尾不再指示该声明具有 IUO 类型;相反,它表明 (1) 声明具有可选类型,并且 (2) 声明具有指示其值可能被隐式强制的属性。 (没有人会编写或观察此属性,但我们将其称为@_autounwrapped。)这样的声明以后称为 IUO 声明。

因此当你使用这个时:

let array = [button1, button2]

编译器将array 类型派生为[UIButton?],因为button1button2 的类型是Optional<UIButton>,而不是ImplicitlyUnwrappedOptional<UIButton>(即使只有一个按钮是可选的,它也会派生可选类型)。

在SE-0054阅读更多内容。

旁注:

此行为与arrays 没有真正的关系,在以下示例中,button2 的类型将派生为UIButton?,即使存在! 并且在button 中设置了一个值:

var button: UIButton! = UIButton()

func foo() 
    let button2 = button // button2 will be an optional: UIButton?

解决方案

如果你想得到一个未包装类型的数组,你有两种选择:

首先,正如Guy Kogus 在他的回答中所建议的,使用显式类型而不是让 swift 派生它:

let array: [UIButton] = [button1, button2]

但是,如果其中一个按钮偶然包含nil,则会导致Unexpectedly found nil 崩溃。

虽然通过使用隐式展开的可选而不是可选(! 而不是 ?)你声称这些按钮中永远不会有 nil,但我仍然更喜欢 second 更安全的选项EmilioPelaez 在他的评论中建议。那就是使用flatMap(Swift 4+ 中的compactMap),它将过滤掉nils(如果有的话),并返回一个未包装类型的数组:

let array = [button1, button2].flatMap  $0 

【讨论】:

完美的解释。谢谢。 @KamilHarasimowicz 很高兴为您提供帮助 我要添加到这个答案的一点是,如果你有一个可选数组,你可以使用array.flatMap $0 ,它将成为一个非可选数组,其中nil 值已被剥离数组。 @EmilioPelaez 感谢您提出改进建议,我已在答案中详细说明【参考方案2】:

因为UIButton! 不是类型,或者更确切地说它是带有一些约定的UIButton?! 意味着总是隐式地打开可选的。以下

 var x: UIButton!
 // Later
 x.label = "foo"

的语法糖
 var x: UIButton?
 // Later
 x!.label = "foo"

当您创建它们的数组时。编译器可以选择隐式展开它们并推断[UIButton] 或将它们保留为可选并推断[UIButton?]。它是两种选择中更安全的一种。

【讨论】:

我不是帖子的作者,但最后一句话让我明白了:) 谢谢 我认为在第二个 sn-p 中应该是 var x: UIButton? 我正确吗?【参考方案3】:

Swift 通过假设它们是可选的而不是默认解包它们来保证安全,因为它们在技术上可以是nil。如果您尝试像这样将它们显式标记为隐式展开

let array: [UIButton!] = [button1, button2]

您将收到以下错误:

错误:隐式展开的选项只允许在顶层和函数结果中使用

在这种情况下,如果您希望它们被解包,那么只需将其定义为

let array: [UIButton] = [button1, button2]

【讨论】:

【参考方案4】:

我发现 Swift 博客确实是进行此类更改的最佳来源,因此:

在 Swift 4 之前:

许多人对隐式展开的可选项的心理模型是,它们是一种类型,不同于常规的可选项。在 Swift 3 中,这正是它们的工作方式:像 var a: Int? 这样的声明将导致具有可选类型,以及像 var b: String! 这样的声明。将导致 b 具有 ImplicitlyUnwrappedOptional 类型。

斯威夫特 4

IUO 的新思维模型是您需要考虑的一种!成为一个 的同义词?此外,它在声明中添加了一个标志 让编译器知道声明的值可以是隐式的 展开。

换句话说,您可以阅读字符串!因为“这个值有类型 可选的,还带有说明它可以的信息 如果需要,可以隐式展开”。

这个心智模型与新的实现相匹配。随处可见 T!,编译器现在将其视为具有类型 T? ,并在其中添加一个标志 它的声明的内部表示让类型检查器 知道它可以在必要时隐式展开值。

所有引用均来自Swift blog

【讨论】:

以上是关于为啥 Swift4 会强制转换 UIButton 数组!到 [UIButton?] 类型?的主要内容,如果未能解决你的问题,请参考以下文章

在发件人中使用带有 UIButton 的标签属性并尝试强制转换 id -> UIButton

为啥在以下结构定义中需要显式强制转换

Swift4 - UIButton 在 UIView 中使用 addTarget 时出错

uitableviewcell 中带有 swift4 部分的 uibutton 的动态数量

Java强制转换为啥没有四舍五入

为啥 UIButton 会覆盖 UITapGestureRecognizer?