Swift中switch case的穷尽条件

42

苹果公司的文档中提到:

每个switch语句都必须是穷尽的,也就是说,考虑到类型的每种可能值都必须有一个对应的case。

因此,在新版 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();
}

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

Switch must be exhaustive
Do you want to add missing cases? Fix

所以我的问题是,对于我已经提到的 case 0...65535: ,我没有提到所有 UInt16 的情况吗?但我仍然会得到一个错误?为什么会出现这个错误?我错过了什么吗?


1
这段代码可以在Xcode 7.3/Swift 2.2下编译通过,因此编译器变得更加智能了 :) - Martin R
2
但它在运行时崩溃了 :( - Martin R
5个回答

96

在使用enum类型时,Swift只有在工作时才会真正验证switch块是否穷举。即使在Bool上进行切换也需要一个default块以及truefalse

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")
}
如果你需要为编译器添加一个默认块,但是又没有任何要执行的内容,可以使用 break 关键字:
var b = true
switch b {
case true:  println("true")
case false: println("false")
default: break
}

1
那么你的意思是iOS声称它是穷尽的,但实际上并不是吗? - Girish Nair
@GirishNair 我不确定我理解了 - 基本上,尽管我们可以确定switch块是穷尽的,但编译器并没有做出确定。因此,即使永远不会触发该模式,我们仍需要提供default: - Nate Cook
5
我刚刚经历了一个非可选的布尔值问题,并认为自己漏掉了什么。我提交了一个错误报告:19582311。 - hidden-username
9
如果你有truefalse的情况,switch对于Bool类型不再需要default。我不确定这个更改是何时发生的,但在Swift 2.2和Xcode 7.3中,上述代码是有效的。 - vacawama
2
@NateCook。刚在xCode 10上检查了一下,如果有true和false,则Bool不需要默认值。不会出现“错误:开关必须详尽”的情况。 - Murali
2
确认;在Swift中,这个答案已经不再正确。我猜测自从Swift 2或3以来就不再适用了。 - Ky -

11
你看到错误的原因之一是编译器不能在不运行代码的情况下验证switch是否穷尽。表达式 0...65535 创建了一个 ClosedInterval 结构体,当 switch 语句执行时,它必须询问该结构体是否包含值 quantity。这样的内容在运行时可能会发生变化,因此编译器无法在编译时检查它。(参见停机问题。)
更一般地说,即使为每个整数值添加特定的 case(case 0: ... case 1: ... ... case 65535:),编译器也无法检测整数值的穷尽开关。编译器不知道你的 switch 是否穷尽。不过,理论上它可以。如果你想看到这个功能,请考虑提交功能请求
目前,有两种情况下 Swift 可以检测完整性并允许省略 default 子句:枚举和元组中的值绑定。@NateCook 回答了枚举 —— 如果你在枚举值上进行 switch,并且在你的 switch 中有一个 case 对应枚举的每个 case,你不需要 default。如果你在元组上进行 switch,并绑定了每种可能的值组合,也不需要 default 标签,如在Swift 书籍中所示
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 值的范围的层次有点微不足道...


1
不行...用 UInt8 试过了,即使有所有256个数字,它也会提示你添加一个默认情况。 - jtbandes
哎呀,这就是我从手机上发布并根据我半记得的内容所得到的结果。:) 编辑了答案并进行了一些更正和补充信息。 - rickster
编译器不运行代码就无法验证 switch 是否穷尽,这种说法并不准确。 - Karoly Horvath
1
现在第三种情况可以不使用 default。如果涵盖了 truefalse,则对 Bool 进行的 switch 不需要默认情况。 - vacawama

2

Swift 4.1。要么你需要指定所有情况,要么在switch语句中包含默认块。


1
(从Swift 4.2开始,可能更早):我有一个帮助函数,将Bool?转换为具有2个段的UISegmentedControl的selectedSegmentIndex。如果该值为nil,则不应选择任何段。我的函数使用switch语句,返回true或false值的适当段索引,并使用它来明确测试nil并满足编译器对其必须是穷尽性的需求:
case nil:  // only remaining possible value
    fallthrough
default:
    return UISegmentedControl.noSegment

从技术上讲,case nil: fallthrough 不是必需的,因为default:已经足够,但如果您想要显式测试一个值以使代码更加自我说明,或者在其他情况下可能有用,那么这种语法可能会有用。

0

检查您的枚举是否被初始化为可选项,可以是case或nil


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接