Swift 5 中切换案例“@unknown default”和“default”之间的区别

Posted

技术标签:

【中文标题】Swift 5 中切换案例“@unknown default”和“default”之间的区别【英文标题】:Difference between switch cases "@unknown default" and "default" in Swift 5 【发布时间】:2019-08-20 00:54:16 【问题描述】:

从 Swift 5 开始,引入了新的 case 属性 @unknown

@unknown 被使用和未被使用的确切区别是什么?在什么情况下我们必须使用@unknown关键字?

【问题讨论】:

请阅读 SE-0192 并观看 this video 了解 Swift 5 中最重要的新闻 【参考方案1】:

来自SE-0192: Handling Future Enum Cases(强调我的):

当切换非冻结的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 中明确处理。为了解决这个问题,switch 案例将获得一个新属性,@unknown

switch excuse 
case .eatenByPet:
  // …
case .thoughtItWasDueNextWeek:
  // …
@unknown default:
  // …

与常规默认值一样,@unknown 默认值匹配任何值;它是 一个“包罗万象”的案例。但是,编译器会在以下情况下产生警告 枚举的所有已知元素尚未匹配。这是 警告而不是错误,以便向枚举添加新元素 仍然是源兼容的更改。 (这也是为什么@unknown default 匹配任何值,而不仅仅是那些在编译时看不到的值。)

@unknown 只能应用于默认值或由 单一模式_。即使在后一种情况下,也必须使用@unknown 与开关中的最后一个案例。进一步讨论此限制 在“未来方向”下的“未知模式”部分。

如果模式中的所有枚举都被匹配,编译器会发出警告 @unknown 被明确注释为冻结,或者如果没有枚举 在模式中。这是一个警告而不是错误,因此 将枚举注释为冻结仍然是与源兼容的更改。如果 该模式包含任何隐式冻结的枚举(即 因为它是一个用户定义的 Swift 枚举),@unknown 是允许的,在 为了更容易适应新增的案例。

@unknown 的缺点是它不是可测试的,因为有 无法创建不匹配任何已知情况的enum 值, 如果有的话,就不会有一种安全的方式来使用它。然而, 将@unknown 与使用fallthrough 的其他情况结合起来可以得到 跟随另一个案例的行为同时仍然获得的效果 新案例的编译器警告。

switch excuse 
case .eatenByPet:
  showCutePicturesOfPet()

case .thoughtItWasDueNextWeek:
  fallthrough
@unknown default:
  askForDueDateExtension()

【讨论】:

作为开发人员,我宁愿编译失败,以防在枚举中添加新值,而不是看到警告。我真的不明白“@unknown”有什么用 @dor506,我认为这个想法是允许 Apple 更新 Swift 标准库,作为未来 ios 更新的一部分,而不会破坏针对早期版本编译的应用程序。我以前认为,标准库包含在每个应用程序的二进制文件中。查看SE-0192 了解更多信息。 @dor506,我更喜欢同样的!【参考方案2】:

在只使用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

【讨论】:

理论上这个新案例听起来很有用,但问题是,您现在将收到一个永久警告,说永远不会使用默认值,并且希望在我的项目中没有警告 @AdamM 是的,但是您只需通过向开关添加另一个案例来修复警告。【参考方案3】:

暗示您将收到关于 您的 枚举警告的答案是错误的。这是关于 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
    

此时我们会收到警告。为什么?

好吧,我们的切换现在已经穷尽了,但它可能不会总是穷尽。如果框架稍后添加案例怎么办?我们编译的代码不会改变,所以当新的 case 到达 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 的特别之处。如果在我们背后添加了另一个案例,编译器会给我们另一个警告告诉我们它,我们可以修复我们的代码以包含它。换句话说,你遵守警告现在来摆脱警告现在,以换取将来可能有用的警告。 p>

这种语法的另一个好处是,如果我们将@unknown default 添加到不是穷举现在的开关,编译器会警告我们那个

【讨论】:

【参考方案4】:

default case

每个 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 语句是详尽无遗的

@unknown default case

来自Reinder's blog post on "What's New In Swift 5.0":

在 Swift 5.0 中,可以将新的 @unknown 关键字添加到 default 开关盒。这不会改变default 的行为,所以这 case 仍将匹配任何未在其余部分处理的案例 switch 块。

switch fruit 
case .apple:
    ... 
@unknown default:
    print("We don't sell that kind of fruit here.")

@unknown 关键字将在 Xcode 中触发警告 处理一个可能并不详尽的switch 语句,因为 一个改变的枚举。你可以慎重考虑这个新案例, 多亏了警告,这是不可能的 default

好消息是,由于default 的工作方式,您的代码不会 如果将新案例添加到枚举中,则中断 - 但您确实会收到警告。 整洁!

更多参考:Hacking with Swift

【讨论】:

"case" before @unknown default 给出错误.. 不需要包含它。【参考方案5】:

如果在此期间扩展案例,编译器会警告您。如果您不使用此关键字,并且稍后扩展案例,您可能会忘记在任何地方更新代码。此关键字可帮助您以后使用。

【讨论】:

以上是关于Swift 5 中切换案例“@unknown default”和“default”之间的区别的主要内容,如果未能解决你的问题,请参考以下文章

使用列表和数组进行切换和大小写

UIPageViewController 不调用委托方法(Swift 5)

Swift 5将使用UTF-8作为首选字符串编码

JavaScript+css+html之菜单栏切换

如何在我的 Xcode Swift 项目的不同导航堆栈中从一个视图控制器屏幕切换到另一个?

Xcode 中使用较低 4.0.3 版本而不是 5.0.1 的 Swift 构建设置未强制执行