从 Swift 5 开始,新的 case 属性 @unknown
被引入。
在使用和不使用 @unknown
的情况下,确切的区别是什么?我们什么时候需要使用 @unknown
关键字?
来源于SE-0192:处理未来的枚举情况(重点是我的):
When switching over a non-frozen
enum
, theswitch
statement that matches against it must include a catch-all case (usuallydefault
or an "ignore"_
pattern).switch excuse { case .eatenByPet: // … case .thoughtItWasDueNextWeek: // … }
Failure to do so will produce a warning in Swift 5. A program will trap at run time if an unknown enum case is actually encountered.
All other uses of enums (
if case
, creation, accessing members, etc) do not change. Only the exhaustiveness checking of switches is affected by the frozen/non-frozen distinction. Non-exhaustive switches over frozen enums (and boolean values) will continue to be invalid in all language modes.Here's a more complicated example:
switch (excuse, notifiedTeacherBeforeDeadline) { case (.eatenByPet, true): // … case (.thoughtItWasDueNextWeek, true): // … case (_, false): // … }
This switch handles all known patterns, but still doesn't account for the possibility of a new enum case when the second tuple element is
true
. This should result in a warning in Swift 5, like the first example.
@unknown
The downside of using a default case is that the compiler can no longer alert a developer that a particular enum has elements that aren't explicitly handled in the switch. To remedy this,
switch
cases will gain a new attribute,@unknown
.switch excuse { case .eatenByPet: // … case .thoughtItWasDueNextWeek: // … @unknown default: // … }
Like the regular default,
@unknown
default matches any value; it is a "catch-all" case. However, the compiler will produce a warning if all known elements of the enum have not already been matched. This is a warning rather than an error so that adding new elements to the enum remains a source-compatible change. (This is also why @unknown default matches any value rather than just those not seen at compile-time.)
@unknown
may only be applied to default or a case consisting of the single pattern _. Even in the latter case,@unknown
must be used with the last case in a switch. This restriction is discussed further in the "unknown patterns" section under "Future directions".The compiler will warn if all enums in the pattern being matched by @unknown are explicitly annotated as frozen, or if there are no enums in the pattern at all. This is a warning rather than an error so that annotating an enum as frozen remains a source-compatible change. If the pattern contains any enums that are implicitly frozen (i.e. because it is a user-defined Swift enum), @unknown is permitted, in order to make it easier to adapt to newly-added cases.
@unknown
has a downside that it is not testable, since there is no way to create anenum
value that does not match any known cases, and there wouldn't be a safe way to use it if there was one. However, combining@unknown
with other cases using fallthrough can get the effect of following another case's behavior while still getting compiler warnings for new cases.
switch excuse { case .eatenByPet: showCutePicturesOfPet() case .thoughtItWasDueNextWeek: fallthrough @unknown default: askForDueDateExtension() }
如果只使用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
(不是错误!),如果枚举的所有已知元素没有匹配(即switch
不完整)。
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枚举编写一个详尽的switch语句:
var err : [URLError.NetworkUnavailableReason] = ...
switch err {
case URLError.NetworkUnavailableReason.cellular: break
case URLError.NetworkUnavailableReason.expensive: break
case URLError.NetworkUnavailableReason.constrained: break
}
此时我们收到一个警告。为什么? 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
}
这意味着:"嘿,编译器,我不希望再出现其他的情况,但如果我将此项目针对框架进行编译并且你发现还有另外一种情况,请警告我,以便我可以明确地将其添加到switch语句中。"
这就是@unknown
的特殊之处。如果背地里添加了另一个情况,编译器将会给我们另一个警告,告诉我们情况的变化,然后我们可以修复代码来包含它。换句话说,你现在服从警告现在,以便在未来可能获得有用的警告。
这种语法的另一个好处是,如果我们将@unknown default
添加到现在不完整的switch语句中,编译器将会警告我们关于那个。
每个switch语句都必须是全面的。也就是说,考虑到的类型的每个可能值都必须与switch case之一匹配。如果不适合为每个可能的值提供一个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")
}
In Swift 5.0, a new
@unknown
keyword can be added to thedefault
switch case. This doesn’t change the behavior ofdefault
, so this case will still match any cases that aren’t handled in the rest of theswitch
block.switch fruit { case .apple: ... @unknown default: print("We don't sell that kind of fruit here.") }
The
@unknown
keyword will trigger a warning in Xcode if you’re dealing with a potentially non-exhaustiveswitch
statement, because of a changed enumeration. You can deliberately consider this new case, thanks to the warning, which wasn’t possible with justdefault
.And the good thing is that due to how
default
works, your code won’t break if new cases are added to the enum – but you do get warned. Neat!
更多参考:Hacking with Swift
编译器会在扩展案例的同时警告您。 如果您不使用此关键字并稍后扩展案例,则可能会忘记在各处更新代码。 此关键字有助于以后使用。