Frozen enums in Swift5
该特性对应的proposal是--Swift evolution-Handling Future Enum Cases
@unknown default
在Swift 4中,这样写Switch case是没有任何问题的
// UIUserInterfaceSizeClass一共就下面三个case
let axis: UIUserInterfaceSizeClass = .unspecified
switch axis {
case .unspecified:
fallthrough
case .regular:
fallthrough
case .compact:
print("")
}
当升级到Swift 5后,编译器会给出一个警告
Switch covers known cases, but 'UIUserInterfaceSizeClass' may have additional unknown values, possibly added in future versions.
Handle unknown values using "@unknown default"
如果接受修改建议,则代码如下
let axis: UIUserInterfaceSizeClass = .unspecified
switch axis {
case .unspecified:
fallthrough
case .regular:
fallthrough
case .compact:
print("")
@unknown default:
fatalError()
}
- 当使用枚举时,尽管已经做到exhausitive了,但升级到Swift 5后仍会有该告警
为什么引入@unknown default
由于Swift要求switch case必须exhausitive,为了满足该要求,在Swift 4及以前,我们使用switch case来进行枚举时,通常只有两种写法:
- 枚举所有case
- 枚举部分case,最后使用default覆盖其他case
但这两种情况都存在各自的问题:
当未来某个版本中,枚举的提供者新增了一个case,那么
- 如果之前使用枚举时枚举了所有case,则会编译出错,因为不满足exhausitive规则
- 如果使用default覆盖了所有case,虽然不会编译出错,但因为未对新增的case进行处理,业务逻辑上可能存在潜在的风险
@unknown default就是用来解决该上面问题的,它其实就做了一件事情:
- 当使用了
@unknown default,但并没有枚举所有case时,编译器会给出未满足exhausitive的警告,而非像``default`那样编译错误
这直接带来的好处是:
- 当使用
@unknown default同时已经枚举了所有case时,当后续枚举提供者新增了case,不会因为编译出错 - 同时鼓励开发者使用
@unknown default替代原来的default,这样后续新增或删减case时,编译器会及时提醒开发者针对enum的变化做必要的调整
一句话总结该属性的目的
既做到不影响开发者源码的可编译性,又能及时提醒开发者做好兼容适配工作
注意,该属性目前并不适用所有的enum,具体看下一小节
@unknown default适用范围
目前仅适用于:
- C中的enum
- 来自系统库如
UIKit、Swift Standard Library中的enum
未来可能会允许开发者自己开发的Library,但目前不适用
我列举了Enum所有可能得情况,对上述适用范围进行了验证
- 在自己工程中定义了C Enum--ProjectSourceTestEnumType
- 在第三方库MJRefresh(OC编写)中定义了C Enum--MJTestEnumType
- 在SnapKit(Swift 编写)中添加了一个Swift自定义Enum--SnapKitTestEnum
func testEnumForCEnum(_ testEnum: MJTestEnumType) {
switch testEnum { // Compiler Warning: Switch covers known cases, but 'MJTestEnumType' may have additional unknown values
case .type1: print("")
case .type2: print("")
}
}
func testEnumForProjectCEnum(_ testEnum: ProjectSourceTestEnumType) {
switch testEnum { // Compiler Warning: Switch covers known cases, but 'ProjectSourceTestEnumType' may have additional unknown values
case .type1: print("")
case .type2: print("")
}
}
func testEnumForThirdLibrary(_ testEnum: SnapKitTestEnum) {
switch testEnum { // 无警告
case .case1: print("")
case .case2: print("")
}
}
为什么限定适用范围呢
为什么非library的Swift枚举不适用
同样的问题是:为什么只有library的枚举适用
- 因为Swift的枚举要求必须是exhausitive的,所以,如果是我们自己定义的枚举中有新增case时,编译器会在语法层面直接报无法满足exhausitive的错误
- @unknown default为的是给开发者一个好的体验,即在某一个枚举未来有新增时,不至于让使用者直接编译错误,而是给一个温柔的告警
为什么C Enum要给告警
或者说,使用代码库中C Enum给告警我忍了,为什么自己工程中的C Enum也给告警呢?
来自官方的解释是
该特性主要适用对象并非enum in project source code,而是project所依赖的enum in library code。说白了就是给代码库开发人员的
- 之所以使用C Enum,是因为C Enum处理起来有点复杂,没办法区分一个C Enum属于project code还是library code,所以进行了统一处理
什么是Frozen enum
到此还没有结束,试想一下,如果所有iOS的系统库中的枚举都使用如上规则,那是不是就相当于通过告警的方式,建议iOS开发者在后续所有进行枚举时都添加@unknown default呢
似乎不太合理。所以引入了Frozen or non Frozen enum的概念,写法如下所示
@frozen public enum SnapKitTestFrozenEnum {
case case1
case case2
}
typedef NS_CLOSED_ENUM(NSUInteger, MJTestClosedEnumType) {
MJTestClosedEnumType1,
MJTestClosedEnumType2
};
- 此时再去枚举上面enum的时候,及时不写
@unknown default,也不会有警告了 - 同时,当library的开发者如果真的往frozen enum中添加了一个case,我们接入后编译器会再次报错,告知违反了exhausitive规则
frozen是冻结的意思,表示不会再改变,比如系统库中的NSComparisonResult ,比较结果只有大、小和相等三种情况,打死也不会出现第三种了,这种就可以标记为frozen了
typedef NS_CLOSED_ENUM(NSInteger, NSComparisonResult) {
NSOrderedAscending = -1L,
NSOrderedSame,
NSOrderedDescending
};
当然,目前大部分枚举还都是non frozen的
总结
对于@unknown default,注意两点:
- 它是方便代码库开发者的feature,方便其在未来添加枚举case时,通过警告而非编译错误的方式通知给代码库使用者
- 注意它的适应范围:所有代码中的C Enum和系统库代码中的Swift或OC编写的enum