Swift 枚举高级用法

6,876 阅读3分钟

原文地址

枚举类型是 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 协议来实现类型转换。

参考:appventure.me/guides/adva…