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

Sar*_*ith 13 swift swift5

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

@unknown使用和不使用时的确切区别是什么?在这种情况下,我们必须使用@unknown关键字?

小智 13

在只使用的情况下,default当 ourswitch不匹配任何选项时使用。让我们看第一个详尽的案例:

enum Option {
  case A
  case B
}

func optionSelected(option: Option) {
  switch(option) {
    case .A:
      print("You chose A!")
    case .B:
      print("You chose B!")
  }
}
Run Code Online (Sandbox Code Playgroud)

这个例子是详尽的,我们不会得到任何错误。但是如果我们需要在我们的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!")
  }
}
Run Code Online (Sandbox Code Playgroud)

在第二个例子中,我们会得到一个错误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!")
  }
}
Run Code Online (Sandbox Code Playgroud)

如果用户选择了选项 C,他将陷入默认情况。但是当我们将选项 D、E 等添加到 Enum 时会发生什么?如果我们不改变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!")
  }
}
Run Code Online (Sandbox Code Playgroud)

如果我们添加选项 D、E 等,我们只会看到警告,然后决定是否要实现其他情况(例如,我们想要选项 D 和 E 的自定义消息),或者我们是否只保留默认消息“您选择了另一个选项”。将其视为友好的余数而不是大的红色错误:)

其他示例:https : //www.raywenderlich.com/55728-what-s-new-in-swift-5

  • 从理论上讲,这个新案例听起来很有用,但问题是,您现在将收到一个永久警告,说永远不会使用默认值,并且希望在我的项目中没有警告 (2认同)

mat*_*att 10

暗示,你将永远得到一个警告的回答你的枚举是错误的。这是关于 Swift 如何处理外部库/框架中的 C(和 Objective-C)枚举的。一个斯威夫特标准库枚举受到影响。

好的,让我们考虑一个实际的例子。我们针对 Cocoa 枚举编写了一个详尽的 switch:

    var err : [URLError.NetworkUnavailableReason] = ...
    switch err {
    case URLError.NetworkUnavailableReason.cellular: break
    case URLError.NetworkUnavailableReason.expensive: break
    case URLError.NetworkUnavailableReason.constrained: break
    }
Run Code Online (Sandbox Code Playgroud)

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

好吧,我们的 switch现在是详尽无遗的,但它可能并不总是详尽无遗。如果框架稍后添加案例怎么办?我们编译的代码不会改变,所以当新案例到达交换机时它会崩溃(陷阱)。

因此,我们需要一种方法,即使框架发生变化,我们的代码也能继续工作。因此,编译器告诉我们:“添加一个默认情况,即使开关是详尽无遗的。”

现在,当然可以添加一个普通的默认情况:

    switch err {
    case URLError.NetworkUnavailableReason.cellular: break
    case URLError.NetworkUnavailableReason.expensive: break
    case URLError.NetworkUnavailableReason.constrained: break
    default: break
    }
Run Code Online (Sandbox Code Playgroud)

问题是如果框架确实发生了变化,我们将永远不会听到它。所以有一个更好的方法,@unknown default

    switch err {
    case URLError.NetworkUnavailableReason.cellular: break
    case URLError.NetworkUnavailableReason.expensive: break
    case URLError.NetworkUnavailableReason.constrained: break
    @unknown default: break
    }
Run Code Online (Sandbox Code Playgroud)

这意味着:“嘿,编译器,我不希望有更多的案例,但是如果我曾经尝试针对框架编译这个项目并且您发现还有另一个案例,请警告我,以便我可以明确添加它到我的交换机。”

所以这就是@unknown. 如果在我们背后添加了另一个案例,编译器会给我们另一个警告告诉我们它,我们可以修复我们的代码以包含它。换句话说,您现在服从警告为了摆脱现在的警告,以换取将来可能有用的警告。

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


Hit*_*esh 8

SE-0192:处理将来的枚举案例(强调我的案例):

切换非冻结的时enumswitch与其匹配的语句必须包含一个全部使用的情况(通常default是“忽略” _模式)。

switch excuse {
case .eatenByPet:
  // …
case .thoughtItWasDueNextWeek:
  // …
}
Run Code Online (Sandbox Code Playgroud)

否则,将在Swift 5中产生警告。如果实际遇到未知的枚举,则程序将在运行时捕获。

枚举的所有其他用法(if case,创建,访问成员等)都不会更改。冻结/非冻结区分仅影响开关的详尽性检查。在所有语言模式下,对冻结的枚举(和布尔值)的非穷举切换将继续无效。

这是一个更复杂的示例:

switch (excuse, notifiedTeacherBeforeDeadline) {
case (.eatenByPet, true):
  // …
case (.thoughtItWasDueNextWeek, true):
  // …
case (_, false):
  // …
}
Run Code Online (Sandbox Code Playgroud)

此开关可处理所有已知模式,但仍不能解决当第二个tuple元素为时出现新枚举的可能性 true。像第一个示例一样,这会在Swift 5中导致警告。

@unknown

使用默认情况的缺点是,编译器无法再警告开发人员特定的枚举包含未在开关中显式处理的元素。为了解决这个问题,switch 案例将获得一个新属性@unknown

switch excuse {
case .eatenByPet:
  // …
case .thoughtItWasDueNextWeek:
  // …
@unknown default:
  // …
}
Run Code Online (Sandbox Code Playgroud)

像常规默认@unknown值一样,默认值匹配任何值。这是一个“包罗万象”的案例。但是,如果枚举的所有已知元素尚未匹配,则编译器将发出警告。这是警告而不是错误,因此向枚举添加新元素仍然是与源兼容的更改。(这也是为什么@unknown default匹配任何值,而不仅仅是匹配在编译时未看到的值的原因。)

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

如果@unknown匹配的模式中的所有枚举都被显式标注为冻结,或者该模式中根本没有枚举,则编译器将发出警告。这是一个警告而不是错误,因此将枚举注释为冻结仍然是与源兼容的更改。如果模式包含任何被隐式冻结的枚举(例如,因为它是用户定义的Swift枚举),则允许@unknown,以使其更易于适应新添加的情况。

@unknown有一个缺点,那就是它是不可测试的,因为无法创建enum与任何已知案例都不匹配的值,并且如果存在的话,将是没有安全的方法来使用它。但是,@unknown与其他案例一起使用fallthrough可以得到遵循另一案例的行为的效果,同时仍然获得针对新案例的编译器警告。

switch excuse {
case .eatenByPet:
  showCutePicturesOfPet()

case .thoughtItWasDueNextWeek:
  fallthrough
@unknown default:
  askForDueDateExtension()
}
Run Code Online (Sandbox Code Playgroud)

  • 作为一名开发人员,我希望在将新值添加到枚举时编译失败,而不是看到警告。我真的不明白“@unknown”有什么用 (5认同)

Anb*_*hik 5

默认情况

每个switch语句必须详尽无遗。也就是说,所考虑类型的每个可能值都必须与切换条件之一匹配。如果不适合为每个可能的值提供大小写,则可以定义默认大小写以覆盖未明确寻址的所有值。此默认情况由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")
}
Run Code Online (Sandbox Code Playgroud)

switch语句的第一个字母与英语字母的第一个字母a匹配,第二个字母与最后一个字母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.")
}
Run Code Online (Sandbox Code Playgroud)

@unknown如果您switch由于枚举更改而正在处理可能不详尽的语句,则该关键字将在Xcode中触发警告。感谢警告,您可以有意考虑这个新案例,而just不可能做到default

好处是,由于default工作原理,如果将新的情况添加到枚举中,您的代码不会中断–但是您会得到警告。整齐!

更多参考:使用Swift进行黑客攻击