枚举类型是 Swift 最显著的特征之一。我们已经看到了很多不同的场景。然而,枚举还可以做更多的事情。
协议
Swift 允许在枚举中使用协议和协议扩展。Swift 中的协议定义了一个接口供其它数据类型来实现。例子如下:
protocol CustomStringConvertible {
var description: String { get }
}
enum Trade: CustomStringConvertible {
case buy, sell
var description: String {
switch self {
case .buy: return "We're buying something"
case .sell: return "We're selling something"
}
}
}
扩展
枚举也可以进行扩展,使用 extension 关键字。下面看下扩展在枚举中的应用场景:
enum Entity {
case soldier(x: Int, y: Int)
case tank(x: Int, y: Int)
case player(x: Int, y: Int)
}
- 为了保持整洁,在枚举的协议扩展中实现所需的协议函数:
extension Entity: CustomStringConvertible {
var description: String {
switch self {
case let .soldier(x, y): return "\(x), \(y)"
case let .tank(x, y): return "\(x), \(y)"
case let .player(x, y): return "\(x), \(y)"
}
}
}
- 将枚举成员和方法分离,使代码清晰易懂:
extension Entity {
mutating func move(dist: CGVector) {}
mutating func attack() {}
}
- 扩展还允许向现有的枚举类型中添加有用的代码。无论是来自 Swift 标准库,还是来自第三方框架,或者是你自己的代码。
extension Optional {
/// Returns true if the optional is empty
var isNone: Bool {
return self == .none
}
}
泛型枚举
枚举的定义中也可以使用泛型参数。例子如下:
enum MyOptional<T> {
case Some(T)
case None
}
let aValue = MyOptional<Int>.Some(5)
let bValue = MyOptional<String>.Some("b value")
Swift 中所有在 class 和 struct 中奏效的类型约束,在 enum 中一样使用:
enum Bag<T: Sequence> where T.Iterator.Element == Equatable {
case empty
case full(contents: [T)]
}
自定义类型作为枚举值
在前一篇文章中,默认情况下,枚举的值只能是整型、浮点、字符串和布尔类型。如果要支持其它类型,需要实现 ExpressibleByStringLiteral 协议,通过对字符串的序列化和反序列化使枚举支持自定义类型。下面看个例子:
enum Devices: CGSize {
case iPhone3GS = "{320, 480}"
case iPhone5 = "{320, 568}"
case iPhone6 = "{375, 667}"
case iPhone6Plus = "{414, 736}"
}
// 通过实现 ExpressibleByStringLiteral 协议 将 String 转化成 CGSize
extension CGSize: ExpressibleByStringLiteral {
public init(stringLiteral value: String) {
let size = NSCoder.cgSize(for: value)
self.init(width: size.width, height: size.height)
}
public init(extendedGraphemeClusterLiteral value: String) {
let size = NSCoder.cgSize(for: value)
self.init(width: size.width, height: size.height)
}
public init(unicodeScalarLiteral value: String) {
let size = NSCoder.cgSize(for: value)
self.init(width: size.width, height: size.height)
}
}
let a = Devices.iPhone5
// 要获取真实的 CGPoint 的值的时,需要访问枚举的是 rawValue 属性
let b = a.rawValue
print("the phone size string is \(a), width is \(b.width), height is \(b.height)")
// the phone size string is iPhone5, width is 320.0, height is 568.0
相等性
对于简单类型的枚举,可以直接使用 == 来判断枚举值是否相等,如下:
enum Toggle {
case on, off
}
if Toggle.on == Toggle.off {
print("is equal!")
} else {
print("is not equal!")
}
// is not equal!
**注意:**这里只能使用 == 和 != 来进行比较,不能使用 > 或 <。
带有关联值的枚举,且关联值类型都实现了 Equatable 协议,则枚举只需要实现 Equatable 协议即可进行相等性判断:
enum Character {
case warrior(name: String, level: Int, strength: Int)
case wizard(name: String, magic: Int, spells: [String])
}
let a = Character.warrior(name: "a", level: 1, strength: 1)
var b = Character.warrior(name: "a", level: 1, strength: 1)
if a == b {
print("is equal!")
} else {
print("is not equal!")
}
// is equal!
b = Character.warrior(name: "b", level: 1, strength: 1)
if a == b {
print("is equal!")
} else {
print("is not equal!")
}
// is not equal!
如果关联值的类型中有未实现 Equatable 协议的类型,则还需要实现 == 运算符,如下:
struct Weapon {
let name: String
}
enum Character: Equatable {
static func == (lhs: Character, rhs: Character) -> Bool {
switch (lhs, rhs) {
case let (.warrior(_, _, _, lWeapon), .warrior(_, _, _, rWeapon)) where lWeapon.name == rWeapon.name:
return true
case let (.wizard(lName, lMagic, lSpells), .wizard(rName, rMagic, rSpells)) where lName == rName && lMagic == rMagic && lSpells == rSpells:
return true
default: return false
}
}
case warrior(name: String, level: Int, strength: Int, weapon: Weapon)
case wizard(name: String, magic: Int, spells: [String])
}
let a = Character.warrior(name: "a", level: 1, strength: 1, weapon: Weapon.init(name: "a"))
var b = Character.warrior(name: "a", level: 1, strength: 1, weapon: Weapon.init(name: "a"))
if a == b {
print("is equal!")
} else {
print("is not equal!")
}
// is equal!
let a = Character.warrior(name: "a", level: 1, strength: 1, weapon: Weapon.init(name: "a"))
var b = Character.warrior(name: "a", level: 1, strength: 1, weapon: Weapon.init(name: "b"))
if a == b {
print("is equal!")
} else {
print("is not equal!")
}
// is not equal!
对 Objective-C 的支持
对于整型枚举,可以通过 @objc 标识直接将其桥接到 Objective-C 中,例子如下:
@objc enum Bit: Int {
case zero = 0
case one = 1
}
对于整型之外的类型,如字符串或带有关联值的枚举,就不能在 Objective-C 中使用该枚举了。需要通过自定义方法来实现类型转换。
enum Trade {
case buy(stock: String, amount: Int)
case sell(stock: String, amount: Int)
}
// This type could also exist in Objective-C code.
@objc class ObjcTrade: NSObject {
var type: Int
var stock: String
var amount: Int
init(type: Int, stock: String, amount: Int) {
self.type = type
self.stock = stock
self.amount = amount
}
}
extension Trade {
func toObjc() -> ObjcTrade {
switch self {
case let .buy(stock, amount):
return ObjcTrade(type: 0, stock: stock, amount: amount)
case let .sell(stock, amount):
return ObjcTrade(type: 1, stock: stock, amount: amount)
}
}
static func fromObjc(source: ObjcTrade) -> Trade? {
switch (source.type) {
case 0: return Trade.buy(stock: source.stock, amount: source.amount)
case 1: return Trade.sell(stock: source.stock, amount: source.amount)
default: return nil
}
}
}
感兴趣的同学也可以研究下通过 _ObjectiveCBridgeable 协议来实现类型转换。