RawOptionSetType
在 Objective-C 中,NS_ENUM
& NS_OPTIONS
被用于注释 C 语言中的 enum
类型,它实现的很漂亮,给编译器和开发者都设置了清晰的期望。自从在 Xcode 4.5 中被引进以后,这两个宏已经成为了系统框架中的标准规范,也是社区公认的最佳实践。
在 Swift 中,枚举类型成了和 struct
与 class
一样的一等语言结构,包含了很多富于表现力的新特性,例如原始类型(raw types)和关联值(associated values)。枚举非常适合于封装一组固定值的封闭集合,开发者们在代码中都很积极地尝试去使用它。
当要在 Swift 中和 Foundation 这样的框架进行交互时,NS_ENUM
定义会自动转换成 enum
。通常情况下和原有的 Objective-C 代码相比,这种转换是一种进步,因为去除了名字当中的重复部分:
enum UITable View Cell Style : Int {
case Default
case Value1
case Value2
case Subtitle
}
typedef NS_ENUM(NSInteger, UITable View Cell Style) {
UITable View Cell Style Default,
UITable View Cell Style Value1,
UITable View Cell Style Value2,
UITable View Cell Style Subtitle
};
不幸的是,对于 NS_OPTIONS
来说,它的 Swift 替代品可以说是相当糟糕:
struct UIView Autoresizing : Raw Option Set Type {
init(_ value: UInt)
var value: UInt
static var None: UIView Autoresizing { get }
static var Flexible Left Margin: UIView Autoresizing { get }
static var Flexible Width: UIView Autoresizing { get }
static var Flexible Right Margin: UIView Autoresizing { get }
static var Flexible Top Margin: UIView Autoresizing { get }
static var Flexible Height: UIView Autoresizing { get }
static var Flexible Bottom Margin: UIView Autoresizing { get }
}
typedef NS_OPTIONS(NSUInteger, UIView Autoresizing) {
UIView Autoresizing None = 0,
UIView Autoresizing Flexible Left Margin = 1 << 0,
UIView Autoresizing Flexible Width = 1 << 1,
UIView Autoresizing Flexible Right Margin = 1 << 2,
UIView Autoresizing Flexible Top Margin = 1 << 3,
UIView Autoresizing Flexible Height = 1 << 4,
UIView Autoresizing Flexible Bottom Margin = 1 << 5
};
Raw
是 NS_OPTIONS
类型在 Swift 当中的替代品(至少是最接近的东西了)。它是一个协议,遵守 Raw
, Equatable
, Bitwise
, 和 Nil
这几个协议。一个选项(option)类型可以用一个遵守 Raw
协议的 struct
表示。
为什么这货这么差劲?主要是因为 C 语言中位运算的技巧不能用于 Swift 中的枚举类型。一个 enum
代表着一系列可用选项的封闭集合,但是并没有内建一个用来表示若干选项的交集的机制。表面上,一个 enum
可以定义出选项值所有可能的组合,但是对于 n > 3
的情况,组合数学告诉我们这种办法是不靠谱的。在 Swift 中实现 NS_OPTIONS
有很多种方式,Raw
可能还不是最差的。
和语法上清晰而明确的 enum
声明相比,Raw
显得笨拙而冗长,需要一些模板代码来支持计算属性(computed properties):
struct Toppings : Raw Option Set Type, Boolean Type {
private var value: UInt = 0
init(_ value: UInt) {
self.value = value
}
// MARK: Raw Option Set Type
static func from Mask(raw: UInt) -> Toppings {
return self(raw)
}
// MARK: Raw Representable
static func from Raw(raw: UInt) -> Toppings? {
return self(raw)
}
func to Raw() -> UInt {
return value
}
// MARK: Boolean Type
var bool Value: Bool {
return value != 0
}
// MARK: Bitwise Operations Type
static var all Zeros: Toppings {
return self(0)
}
// MARK: Nil Literal Convertible
static func convert From Nil Literal() -> Toppings {
return self(0)
}
// MARK: -
static var None: Toppings { return self(0b0000) }
static var Extra Cheese: Toppings { return self(0b0001) }
static var Pepperoni: Toppings { return self(0b0010) }
static var Green Pepper: Toppings { return self(0b0100) }
static var Pineapple: Toppings { return self(0b1000) }
}
在 Xcode 6 Beta 6 中,
Raw
不再遵守Option Set Type Boolean
协议,如果想支持按位检查的话还是需要支持Type Boolean
。Type
在 Swift 中这种写法的一个好处是,Swift 内建的二进制整数字面值支持进行视觉上的按位运算。当 options 类型定义完成之后,使用它的语法还不算特别难看。
以下面这个大一些的例子为例:
struct Pizza {
enum Style {
case Neopolitan, Sicilian, New Haven, Deep Dish
}
struct Toppings : Raw Option Set Type { ... }
let diameter: Int
let style: Style
let toppings: Toppings
init(inches In Diameter diameter: Int, style: Style, toppings: Toppings = .None) {
self.diameter = diameter
self.style = style
self.toppings = toppings
}
}
let dinner = Pizza(inches In Diameter: 12, style: .Neopolitan, toppings: .Pepperoni | .Green Pepper)
对于值的归属检查,可以使用 &
运算符,就像在 C 中操作无符号整数一样:
extension Pizza {
var is Vegetarian: Bool {
return toppings & Toppings.Pepperoni ? false : true
}
}
dinner.is Vegetarian // false
平心而论,现在来谈论选项(option)类型在 Swift 语言当中的角色还为时过早。很有可能 Swift 中的其他结构,例如元组(tuple)和模式匹配(pattern matching),也可能就是 enum
本身,会让选项类型变得不仅仅只是来自过去的遗迹。
不管怎样,如果你想在代码中实现类似 NS_OPTIONS
的结构,下面是一段 Xcode snippet,可以帮助你快速上手:
struct Options : Raw Option Set Type, Boolean Type {
let raw Value: UInt
init(nil Literal: ()) { self.value = 0 }
init(_ value: UInt = 0) { self.value = value }
init(raw Value value: UInt) { self.value = value }
var bool Value: Bool { return value != 0 }
var raw Value: UInt { return value }
static var all Zeros: Options { return self(0) }
static var None: Options { return self(0b0000) }
static var Option: Options { return self(0b0001) }
// ...
}
```