Swift中开关盒的详尽条件

Posted

技术标签:

【中文标题】Swift中开关盒的详尽条件【英文标题】:Exhaustive condition of switch case in Swift 【发布时间】:2014-11-01 05:41:38 【问题描述】:

苹果documentation 说

每个 switch 语句都必须是详尽的。也就是说,所有可能的 正在考虑的类型的值必须与以下之一匹配 切换案例。

所以在新的 Xcode 中我放置了这样的代码

println(UInt16.min); // Output : '0'
println(UInt16.max); // Output : '65535'

var quantity : UInt16 = 10;

switch quantity 
case 0...65535: //OR case UInt16.min...UInt16.max:
    println();
default:
    println();

现在,如果我删除默认部分,我会收到编译器错误:

切换必须是详尽的 您要添加缺失的案例吗?修复

所以我的问题是针对我提到的 case 0...65535: 的案例,我没有提到 UInt16 的所有案例值吗?但我仍然收到错误??为什么会出现这个错误,我错过了什么吗??

【问题讨论】:

这段代码用 Xcode 7.3/Swift 2.2 编译,所以编译器变得更聪明了:) ...但它在运行时崩溃:( 【参考方案1】:

Swift 仅在使用 enum 类型时才真正验证 switch 块是否详尽无遗。除了truefalse 之外,即使打开Bool 也需要default 块:

var b = true
switch b 
case true:  println("true")
case false: println("false")

// error: switch must be exhaustive, consider adding a default clause

但是,对于 enum,编译器很乐意只查看两种情况:

enum MyBool 
    case True
    case False


var b = MyBool.True
switch b 
case .True:  println("true")
case .False: println("false")

如果您为了编译器需要包含一个default 块,但又没有任何事情要做,那么break 关键字就派上用场了:

var b = true
switch b 
case true:  println("true")
case false: println("false")
default: break

【讨论】:

所以你的意思是ios说它详尽,但实际上不是? 我刚刚用非可选的 Bool 体验了这一点,并认为我错过了一些东西。我提交了一个错误报告:19582311 switch 不再需要 default 用于 Bool 如果您有 truefalse 的案例。我不确定这个变化是什么时候发生的,但是在 Xcode 7.3 中使用 Swift 2.2,上面的工作。 @NateCook 。刚刚检查了 xCode 10,如果存在 true 和 false,则 Bool 不需要默认值。 “错误:开关必须详尽”不会出现 已确认;这个答案在 Swift 中不再适用。我怀疑从 Swift 2 或 3 开始。【参考方案2】:

您看到该错误的部分原因是编译器无法在不运行代码的情况下验证 switch 是否详尽。表达式0...65535 创建了一个ClosedInterval 结构,当switch 语句执行时,它必须询问该结构quantity 的值是否在区间内。在运行时有更改的空间,因此编译器无法在编译时检查它。 (见Halting Problem。)

更一般地说,编译器无法检测到整数值的穷举开关——即使你为每个整数值添加特定情况 (case 0: ... case 1: ... ... case 65535:),它也不知道你的开关是穷举的。 (但理论上它可以:如果您想看到,请考虑 filing a feature request。)

目前,Swift 可以在两种情况下检测完整性并允许您省略 default 子句:元组中的枚举和值绑定。 @NateCook 的回答涵盖了枚举——如果你打开一个枚举值并且在你的switch 中为枚举中的每个case 有一个case,你就不需要default。如果您打开一个元组并绑定所有可能的值组合,您也不需要default 标签,如in the Swift book 所示:

switch anotherPoint 
case (let x, 0):
    println("on the x-axis with an x value of \(x)")
case (0, let y):
    println("on the y-axis with a y value of \(y)")
case let (x, y):
    println("somewhere else at (\(x), \(y))")

您可以将此规则概括为“如果类型系统知道您的类型的可能值,它可以检测到switch 完整性”,但事实上存在一个类型系统不知道范围的级别可能的(例如)UInt32 值有点像分裂的头发......

【讨论】:

不...用UInt8 尝试过,它会提示您添加一个默认大小写,即使是所有 256 个数字。 嗯,这就是我从手机发帖并忘记了我记得一半的内容。 :) 修改后的答案和一些进一步的信息。 “编译器无法在不运行代码的情况下验证 switch 是否详尽”——这根本不是真的。 第三种情况现在可以在没有default 的情况下工作。如果 truefalse 都被覆盖,Bool 上的 switch 不需要默认大小写。【参考方案3】:

斯威夫特 4.1。要么您需要指定所有情况,要么只在 switch 语句中包含默认块。

【讨论】:

【参考方案4】:

(从 Swift 4.2 开始,可能更早):我有一个辅助函数,可以将 Bool? 转换为 selectedSegmentIndex 用于具有 2 个段的 UISegmentedControl。如果值为nil,则不应选择任何段。我的函数使用了一个开关,它为真或假值返回适当的段索引,并使用它来显式测试nil 并满足编译器对其详尽无遗的需求:

case nil:  // only remaining possible value
    fallthrough
default:
    return UISegmentedControl.noSegment

从技术上讲,case nil: fallthrough 不是必需的,因为 default: 就足够了,但是如果您想显式测试一个值以使代码更具自我记录性,或者可能在另一种情况。

【讨论】:

【参考方案5】:

检查你的枚举是否被初始化为可选项,可以是大小写,也可以是 nil

【讨论】:

以上是关于Swift中开关盒的详尽条件的主要内容,如果未能解决你的问题,请参考以下文章

使用开关盒的三个数字中的最大值[关闭]

贝宝沙盒的负面测试不再可用?

减少非空开关盒的数量

带有开关盒的 JOptionPane

什么是大型开关盒的好替代品?

开关盒的样式