从 Swift 5 开始,引入了新的 case 属性@unknown
。
@unknown
使用和不使用的确切区别是什么?在什么情况下我们必须使用@unknown
关键字?
来自SE-0192:处理未来的枚举案例(强调我的):
切换 non-frozen
enum
时,与其匹配的switch
语句必须包含一个包罗万象的案例(通常default
是“忽略”_
模式)。switch excuse { case .eatenByPet: // … case .thoughtItWasDueNextWeek: // … }
不这样做将在 Swift 5 中产生警告。如果实际遇到未知的枚举情况,程序将在运行时陷阱。
枚举的所有其他用途(
if case
、创建、访问成员等)都不会改变。只有开关的详尽检查受冻结/非冻结区别的影响。在所有语言模式下,冻结枚举(和布尔值)的非详尽切换将继续无效。这是一个更复杂的例子:
switch (excuse, notifiedTeacherBeforeDeadline) { case (.eatenByPet, true): // … case (.thoughtItWasDueNextWeek, true): // … case (_, false): // … }
这个开关处理所有已知的模式,但仍然没有考虑到当第二个元组元素是
true
. 这应该会导致 Swift 5 中出现警告,就像第一个示例一样。
@unknown
使用默认情况的缺点是编译器无法再提醒开发人员特定枚举具有未在开关中显式处理的元素。为了解决这个问题,
switch
案例将获得一个新属性,@unknown
。switch excuse { case .eatenByPet: // … case .thoughtItWasDueNextWeek: // … @unknown default: // … }
与常规默认值一样,
@unknown
默认值匹配任何值;这是一个“包罗万象”的案例。但是,如果枚举的所有已知元素尚未匹配,编译器将产生警告。这是一个警告而不是错误,因此向枚举添加新元素仍然是与源兼容的更改。(这也是为什么@unknown default 匹配任何值而不仅仅是那些在编译时看不到的值。)
@unknown
只能应用于 default 或由单个模式 _ 组成的情况。即使在后一种情况下,也@unknown
必须在 switch 中与最后一种情况一起使用。此限制将在“未来方向”下的“未知模式”部分中进一步讨论。如果 @unknown 匹配的模式中的所有枚举都被显式注释为冻结,或者模式中根本没有枚举,编译器将发出警告。这是一个警告而不是错误,因此将枚举注释为冻结仍然是与源兼容的更改。如果模式包含任何隐式冻结的枚举(即因为它是用户定义的 Swift 枚举),则允许使用@unknown,以便更容易适应新添加的情况。
@unknown
有一个缺点是它不可测试,因为没有办法创建一个enum
不匹配任何已知情况的值,如果有的话,也不会有安全的方法来使用它。但是,@unknown
使用 fallthrough 与其他案例相结合可以获得遵循另一个案例行为的效果,同时仍会收到新案例的编译器警告。switch excuse { case .eatenByPet: showCutePicturesOfPet() case .thoughtItWasDueNextWeek: fallthrough @unknown default: askForDueDateExtension() }
在仅 using 的情况下default
,它被用作当我们switch
不匹配任何选项时。让我们看第一个详尽的案例:
enum Option {
case A
case B
}
func optionSelected(option: Option) {
switch(option) {
case .A:
print("You chose A!")
case .B:
print("You chose B!")
}
}
这个例子很详尽,我们不会得到任何错误。但是如果我们需要在我们的enum
?
enum Option {
case A
case B
case C
}
func optionSelected(option: Option) {
switch(option) {
case .A:
print("You chose A!")
case .B:
print("You chose B!")
}
}
在第二个例子中,我们会得到一个错误Switch must be exhaustive
。为了避免这个错误,我们可能会实现一个默认情况:
enum Option {
case A
case B
case C
}
func optionSelected(option: Option) {
switch(option) {
case .A:
print("You chose A!")
case .B:
print("You chose B!")
default:
print("You chose other option!")
}
}
如果用户选择选项 C,他将陷入默认情况。但是当我们在枚举中添加选项 D、E 等时会发生什么?如果我们不改变它们,switch
它们都会陷入default
. 这可能不是问题,具体取决于您要实现的内容。
现在,使用,我们继续捕获所有其他选项,但这里的不同之处在于,如果枚举的所有已知元素都没有匹配(即开关不匹配)@unknown
,编译器会发出警告(不是错误!)Switch must be exhaustive
详尽)。
enum Option2 {
case A
case B
case C
}
func optionSelected2(option: Option2) {
switch(option) {
case .A:
print("You chose A!")
case .B:
print("You chose B!")
case .C:
print("You chose C!")
@unknown default:
print("You chose other option!")
}
}
如果我们添加选项 D、E 等,我们只会看到一个警告,然后决定是否要实现其他情况(例如,我们想要选项 D 和 E 的自定义消息)或者我们是否只保留默认消息“您选择了另一个选项”。把它想象成一个友好的余数,而不是一个大的红色错误:)
其他示例:https ://www.raywenderlich.com/55728-what-s-new-in-swift-5
暗示您将收到枚举警告的答案是错误的。这是关于 Swift 如何处理外部库/框架中的 C(和 Objective-C)枚举。一些Swift标准库枚举受到影响。
好的,让我们考虑一个实际的例子。我们针对 Cocoa 枚举编写了一个详尽的开关:
var err : [URLError.NetworkUnavailableReason] = ...
switch err {
case URLError.NetworkUnavailableReason.cellular: break
case URLError.NetworkUnavailableReason.expensive: break
case URLError.NetworkUnavailableReason.constrained: break
}
此时我们会收到警告。为什么?
好吧,我们的 switch现在是详尽的,但它可能并不总是详尽的。如果框架稍后添加案例怎么办?我们编译的代码不会改变,因此当新案例到达交换机时它会崩溃(陷阱)。
所以我们需要一种方法来让我们的代码即使在框架发生变化的情况下也能继续工作。因此,编译器告诉我们:“添加一个默认情况,即使开关是详尽无遗的。”
现在,当然可以添加一个普通的默认情况:
switch err {
case URLError.NetworkUnavailableReason.cellular: break
case URLError.NetworkUnavailableReason.expensive: break
case URLError.NetworkUnavailableReason.constrained: break
default: break
}
问题在于,如果框架确实发生了变化,我们将永远不会听到它。所以有一个更好的方法,@unknown default
:
switch err {
case URLError.NetworkUnavailableReason.cellular: break
case URLError.NetworkUnavailableReason.expensive: break
case URLError.NetworkUnavailableReason.constrained: break
@unknown default: break
}
这意味着:“嘿,编译器,我不希望有更多案例,但是如果我尝试针对框架编译这个项目并且您发现还有另一种案例,请警告我,以便我可以显式添加它到我的开关。”
所以这就是它的特别之处@unknown
。如果在我们背后添加了另一个案例,编译器会给我们另一个警告,告诉我们它,我们可以修复我们的代码以包含它。换句话说,你现在服从警告,就是为了摆脱现在的警告,以换取将来可能有用的警告。
这种语法的另一个好处是,如果我们向一个现在并不详尽@unknown default
的开关添加一个,编译器会警告我们。
每个 switch 语句都必须是详尽的。也就是说,所考虑的类型的每个可能值都必须与其中一个 switch case 匹配。如果不适合为每个可能的值提供大小写,您可以定义一个默认大小写来覆盖任何未明确解决的值。此默认大小写由 default 关键字指示,并且必须始终出现在最后。
例如:
let someCharacter: Character = "z"
switch someCharacter {
case "a":
print("The first letter of the alphabet")
case "z":
print("The last letter of the alphabet")
default:
print("Some other character")
}
switch 语句的第一个 case 匹配英文字母的第一个字母 a,第二个 case 匹配最后一个字母 z。因为 switch 必须对每个可能的字符都有一个大小写,而不仅仅是每个字母字符,所以这个 switch 语句使用默认大小写来匹配除 a 和 z 之外的所有字符。此规定确保 switch 语句是详尽无遗的
来自Reinder 关于“Swift 5.0 的新功能”的博文:
在 Swift 5.0 中,
@unknown
可以在default
switch case 中添加一个新的关键字。这不会改变 的行为default
,因此这种情况仍将匹配switch
块的其余部分未处理的任何情况。switch fruit { case .apple: ... @unknown default: print("We don't sell that kind of fruit here.") }
@unknown
如果您正在处理可能不是详尽的switch
语句,则该关键字将在 Xcode 中触发警告,因为枚举发生了变化。由于警告,您可以有意识地考虑这个新案例,这在 just 中是不可能的default
。好消息是,由于
default
工作原理,如果将新案例添加到枚举中,您的代码不会中断 - 但您确实会收到警告。整洁的!
更多参考:使用 Swift 进行黑客攻击
如果在此期间扩展案例,编译器会警告您。如果您不使用此关键字,并且稍后扩展案例,您可能会忘记在任何地方更新代码。此关键字可帮助您以后使用。