[译] Swift 5 中的枚举冻结

13,424 阅读5分钟

你是否已经将你的 Xcode 工程升级到了兼容 Swift 5?升级 Swift 5 基本不会有大问题,但当你用 switch 语句去枚举一个未知值的时候可能会遇到很多警告。。

其他未知值的警告

到底是什么问题呢?我现在用一小段代码展示为不同大小的类适配布局。它枚举出了 UIUserInterfaceSizeClass所有可能值

func configure(for sizeClass: UIUserInterfaceSizeClass) {
    switch sizeClass {
    case .uppercased:
        // ...
    case .compact:
        // ...
    case .regular:
        // ...
    }
}

当我把项目升级到 Swift 5 之后,Xcode 会报出一些警告:

additional unknown values warning

警告内容:

Switch 语句涵盖了所有 case,但是 UIUserInterfaceSizeClass 以后可以会添加一些其他未知的值

请使用 “@unknown default” 来处理未知值

如果你点击了修复,Xcode 会自动添加一个 @unknown default: 的 case:

switch sizeClass {
case .unspecified:
    // ...
case .compact:
    // ...
case .regular:
    // ...
@unknown default:
    fatalError()
}

你可以修改 fatalError(),但这到底是什么意思呢?

冻结和非冻结的枚举

Swift 中的一个 switch 语句必须是详尽的或者包含一个默认的 default case 来处理其他所有情况。我原来的 switch 语句把在 Apple 在 iOS 12 的 UIKit 中定义的 UIUserInterfaceSizeClass 的所有可能值都进行了枚举。

如果 Apple 在 iOS 13 中引入了 .tiny.large,那会发生什么呢?使用新版本的 SDK 编译时,编译器会因 switch 语句不再详尽而报错。解决错误的的一种方法是引入一个 default: case。我可以重写我的 switch 语句:

switch sizeClass {
   case .compact:
      // 布局为 compact 该做的事
   default:
      // 默认情况下该做的事
      // 其中也包含了一些未知情况
  }

该 switch 不再详尽,但它能处理所有现在已知和未来的未知的情况。这一点很好,但如果 switch 是详尽的,这对编译器就有优势,并且它在编译时能兼容二进制库。当枚举更新后有新的 case,你可能还需要警告。

Swift 进化的提案 SE-0192 添加了 @unknown default: 语法,这允许你可以继续使用一个详尽的 switch 来用于未来可能出现的情况:

switch sizeClass {
case .unspecified:
    // ...
case .compact:
    // ...
case .regular:
    // ...
@unknown default:
    // ...
}

@unknown default: 这个 case 只能用于让枚举变得详尽,并匹配添加到枚举的任何新案例。它依旧会为这些新 case 生成警告,以便你可以决定采取怎么样的操作。这不同于使用 default: 和一个非详尽的枚举,它不会提示你有新的 case 未处理。

冻结枚举

这一变化还增加了 冻结枚举 的概念,这不是为了获得任何新 case。它仅适用于在 Swift 里导入 C 或 Objective-C 的枚举。举个例子,标准库中的 ComparisonResult。这是他在 Objective-C 中的定义:

typedef NS_CLOSED_ENUM(NSInteger, NSComparisonResult) {
    NSOrderedAscending = -1L,
    NSOrderedSame,
    NSOrderedDescending
};

只有三种可能的情况,所以这个枚举永远不会改变。注意 NS_CLOSED_ENUM 注释而不是通常的 NS_ENUM。我们并不需要在 switch 里添加 @unknown default 来让它变得详尽:

let result: ComparisonResult = ...
switch result {
    case .orderedAscending:
        // ...
    case .orderedSame:
        // ...
    case .orderedDescending:
        // ...
}

如果库的作者将新 case 添加到了冻结枚举,则编译会报错。

冻结还是非冻结?

我们不知道 Apple 是否计划在 UIKit 和相关框架中枚举的情况。大多数可能是非冷冻的,但在某些情况下,冷冻可能是有意义的。

例如,堆栈视图轴是一个 UILayoutConstraintAxis 的枚举,在 iOS 12 中仍未冻结。这是 Objective-C 的头文件(注意 NS_ENUM):

typedef NS_ENUM(NSInteger, UILayoutConstraintAxis) {
    UILayoutConstraintAxisHorizontal = 0,
    UILayoutConstraintAxisVertical = 1
};

这意味着如果要打开堆栈视图轴,则需要允许将来可能的未知情况:

switch stackView.axis {
case .horizontal:
    // ...
case .vertical:
    // ...
@unknown default:
    // ...
}

也许 Apple 会将其更改为 NS_CLOSED_ENUM,又或者堆栈视图是否会在 iOS 13 有别的可能值?

我们需要做什么?

  • 首先,你不需要改变你自己原先的 Swift 枚举。当你升级到 Swift 5 时,你不需要一开始就手动在代码上添加 @unknown default:。Xcode 会发出警告来提示你需要添加的位置。
  • 此更改仅适用于标准库和其他框架中的 C 语言枚举。
  • 如果您的 switch 包含 default: case,那就不需要进行任何变化。
  • 彻底切换 C 语言的非冻结枚举,包括所有已知的情况(不含 default:)是 Swift 5 中的警告。
  • 你可以让 Xcode 帮你自动修复代码,它会添加一个 @unknown: 的 case 并消除警告。

阅读更多

有关更多详细信息,请参阅 Swift 进化的提案:

如果发现译文存在错误或其他需要改进的地方,欢迎到 掘金翻译计划 对译文进行修改并 PR,也可获得相应奖励积分。文章开头的 本文永久链接 即为本文在 GitHub 上的 MarkDown 链接。


掘金翻译计划 是一个翻译优质互联网技术文章的社区,文章来源为 掘金 上的英文分享文章。内容覆盖 AndroidiOS前端后端区块链产品设计人工智能等领域,想要查看更多优质译文请持续关注 掘金翻译计划官方微博知乎专栏