为啥 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?
。为什么?
【问题讨论】:
因为button1
和button2
是可选的。
Swift 无法创建[UIButton!]
数组吗?
你为什么用!
强制解包?您是否正在尝试创建一组非可选 UIButtons?通常,使用!
解包仅在类初始化时对象为 nil 时使用,但稍后肯定会初始化。
@KamilHarasimowicz 你不能有[UIButton!]
- 根据SE-0054,T!
不再是一个独特的类型;它现在与T?
的类型相同,但它允许将属性附加到声明(例如属性),从而允许在某些情况下强制解包。比较***.com/q/38849549/2976878、***.com/q/39537177/2976878、***.com/q/39633481/2976878 &
@Stephen 假设button1
和button2
是网点,foo()
函数将自定义它们。
【参考方案1】:
解释
ImplicitlyUnwrappedOptional
不是一个独特的类型,而是一个普通的Optional
,其属性声明其值可能是隐式强制的(基于SE-0054):
但是,外观!在属性或变量声明的类型的末尾不再指示该声明具有 IUO 类型;相反,它表明 (1) 声明具有可选类型,并且 (2) 声明具有指示其值可能被隐式强制的属性。 (没有人会编写或观察此属性,但我们将其称为@_autounwrapped。)这样的声明以后称为 IUO 声明。
因此当你使用这个时:
let array = [button1, button2]
编译器将array
类型派生为[UIButton?]
,因为button1
和button2
的类型是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
),它将过滤掉nil
s(如果有的话),并返回一个未包装类型的数组:
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 时出错