Swift 4.2 在 WWDC 18 上正式公布,作为 Xcode 10 搭载的 Swift 最新版,以及 Swift 5 到来之前的重要一步,它有哪些新特性呢?首先我们来了解一下CaseIterable
合成枚举 allCases
Swift 4.2 引入一个新的 protocol CaseIterable
,它被用于合成简单枚举类型的 allCases
静态属性,代码如下:
enum Weekday : String, CaseIterable {
case monday, tuesday, wednesday, thursday, friday
}
print(Weekday.allCases)
所谓“简单枚举类型”,指的是不带关联值的枚举类型。所以,如果上述 Weekday 枚举声明成 enum Weekday : String, CaseIterable
的话(指定rawType),编译器也是能够自动合成的。
Swift 支持一些标准库类型的自动合成,在Swift 4.1 中,我们曾就提到过 合成的Equatable 和 Hashable以及 Codable。所以 CaseIterable 是另一个支持自动合成的 -able 协议。
然而,自动合成特性尽管方便,但是也会造成记忆成本,大家需要记住什么时候会合成而什么时候不会。另一方面,因为方便,那么对实际概念可能不求胜解。例如,Codable的改进 中的 CodingKey 由于编译器支持自动合成,那么大家对 CodingKey 的实际理解可能就会流于声明的表象。
自实现 CaseIterable
当无法自动合成的时候,我们可以自己实现 CaseIterable。例如,带关联值的枚举:
enum MartialStatus : CaseIterable {
case single
case married(spouse: String)
static var allCases: [MartialStatus] {
return [.single, .married(spouse: "Leon")]
}
}
再比如,某个case在某种情况下不可用,或者默认合成的实现不是你想要的时候,也可以自己实现。
enum MartialStatus : CaseIterable {
@available(*, unavailable)
case single
case married
static var allCases: [MartialStatus] { return [.married]}
}
print(MartialStatus.allCases)
也许你会好奇,既然某些情况下编译器不能合成,那么我们完全可以自己添加 allCases 属性,而不声明实现 CaseIterable。首先,这样是可以的,因为以前就是这么做的。其次,你也有可能会失去一些其他方面针对 CaseIterable 协议设计的一些功能;当然到需要的时候,再通过extension来扩展声明实现也是可以的。
详解 CaseIterable
CaseIterable 如同 Equatable 和 Hashable 一样,是泛型约束,而不是泛型类型,它的具体定义如下:
public protocol CaseIterable {
/// A type that can represent a collection of all values of this type.
associatedtype AllCases : Collection where Self.AllCases.Element == Self
/// A collection of all values of this type.
static var allCases: Self.AllCases { get }
}
首先,它定义类一个关联类型 AllCases 作为 allCases 的返回值,然后约束这个类型是一个 Collection,并且 Element 和 这个枚举一致。我们之前的例子使用了Array,因此满足这个约束。
当然,这个 CaseIterable 也可以简单些,比如这样:
public protocol CaseIterable2 {
static var allCases: [Self] { get }
}
这当然也是可以的,缺点是没有那么面向协议了。标准库之所以采用了约束的形式,是希望不跟任何具体 Collection 类型耦合。所以我们好奇地想知道,编译器合成 CaseIterable 是使用了哪个具体的 Collection 类型呢?
enum Weekday : String, CaseIterable {
case monday, tuesday, wednesday, thursday, friday
}
print(type(of:Weekday.allCases))
原来还是 Array<Weekday>
。
小结
本文,我们详细讨论了 CaseIterable:
- CaseIterable 编译器合成的条件
- 自己实现 CaseIterable
- CaseIterable 的定义