Ios 为什么Swift4会抛出一系列UIButton!到[UIButton?]类型?

Ios 为什么Swift4会抛出一系列UIButton!到[UIButton?]类型?,ios,swift,swift4,Ios,Swift,Swift4,我今天遇到了一个奇怪的问题。请看下面的代码: class A { var button1: UIButton! var button2: UIButton! func foo() { let array = [button1, button2] } } Xcode表示数组是[UIButton?]类型。出于某种原因,Swift4会播放ui按钮元素到ui按钮?。为什么?斯威夫特为了安全起见,假定它们是可选的,而不是在默认情况下展开它们,因为它们在

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

class A {

    var button1: UIButton!
    var button2: UIButton!

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

Xcode表示
数组
[UIButton?]
类型。出于某种原因,Swift4会播放
ui按钮元素到
ui按钮?
。为什么?

斯威夫特为了安全起见,假定它们是可选的,而不是在默认情况下展开它们,因为它们在技术上可以是
nil
。如果您试图像这样显式地将它们标记为隐式展开

let array: [UIButton!] = [button1, button2]
您将收到以下错误:

错误:隐式展开的选项仅允许在顶层和作为函数结果

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

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

解释

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

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

因此,当您使用此选项时:

let array = [button1, button2]
编译器将
数组
类型派生为
[UIButton?]
,因为
按钮1
按钮2
的类型是
可选的
,而不是
隐式的
(即使只有一个按钮是可选的,它也会派生可选类型)

阅读更多信息

旁注:

此行为实际上与
数组
无关,在下面的示例中,
按钮2
的类型将被派生为
UIButton?
,即使存在
并且在
按钮中设置了一个值

var button: UIButton! = UIButton()

func foo() {
    let button2 = button // button2 will be an optional: UIButton?
}
解决方案

如果要获取未包装类型的数组,有两个选项:

首先,正如他在回答中所建议的,使用显式类型,而不是让斯威夫特派生:

let array: [UIButton] = [button1, button2]
但是,如果其中一个按钮可能包含
nil
,则会导致
意外发现nil
崩溃

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

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

因为
ui按钮
不是一种类型,或者更确切地说,它是一种带有某些约定的
UIButton?
表示始终隐式展开可选的。以下

 var x: UIButton!
 // Later
 x.label = "foo"
语法上的糖是什么

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

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

我发现Swift博客确实是这些变化的最佳来源,因此:

在Swift 4之前:

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

Swift 4

< IUOS的新心智模型是你考虑的一个模型!当家 同义词?它在声明中添加了一个标志 让编译器知道声明的值可以隐式 打开包装

换句话说,您可以读取字符串!as“此值的类型为 可选,还包含表示可以 如果需要,隐式展开”

此心智模型与新实现相匹配。无论你身在何处 T编译器现在将其视为具有类型T,并在中添加一个标志 其声明的内部表示形式允许类型检查器 知道它可以在必要时隐式展开值


因为
按钮1
按钮2
是可选的,所以所有引用都取自。Swift无法创建
[UIButton!]
数组吗?为什么要使用
强制展开?您正在尝试创建非可选UIButton数组吗?通常使用
展开仅在初始化类时对象为零时使用,但以后肯定会初始化。@KamilHarasimowicz您不能有
[UIButton!]
–根据,
T不再是独特的类型;现在它的类型与
T?
相同,但它允许将属性附加到声明(例如属性)中,从而允许在某些情况下强制展开该属性。比较,&@Stephen让我们假设
button1
button2
是插座,
foo()
函数将自定义它们。我不是这篇文章的作者,但最后一句话对我说得很清楚:)谢谢,我认为第二段应该是
var x:UIButton?
我说得对吗?完美的解释。谢谢。@KamilHarasimowicz很高兴能为您提供帮助。我想补充一点,如果您有一个可选数组,您可以使用
array.flatMap{$0}
,它将成为一个非可选数组,其中
nil
值已从数组中删除。@EmilioPelaez感谢您的改进建议,我在回答中详细阐述了这一点