Raw​Option​Set​Type

在 Objective-C 中,NS_ENUM & NS_OPTIONS被用于注释 C 语言中的 enum 类型,它实现的很漂亮,给编译器和开发者都设置了清晰的期望。自从在 Xcode 4.5 中被引进以后,这两个宏已经成为了系统框架中的标准规范,也是社区公认的最佳实践。

在 Swift 中,枚举类型成了和 structclass 一样的一等语言结构,包含了很多富于表现力的新特性,例如原始类型(raw types)和关联值(associated values)。枚举非常适合于封装一组固定值的封闭集合,开发者们在代码中都很积极地尝试去使用它。

当要在 Swift 中和 Foundation 这样的框架进行交互时,NS_ENUM 定义会自动转换成 enum。通常情况下和原有的 Objective-C 代码相比,这种转换是一种进步,因为去除了名字当中的重复部分:

enum UITableViewCellStyle : Int {
    case Default
    case Value1
    case Value2
    case Subtitle
}

不幸的是,对于 NS_OPTIONS 来说,它的 Swift 替代品可以说是相当糟糕:

struct UIViewAutoresizing : RawOptionSetType {
    init(_ value: UInt)
    var value: UInt
    static var None: UIViewAutoresizing { get }
    static var FlexibleLeftMargin: UIViewAutoresizing { get }
    static var FlexibleWidth: UIViewAutoresizing { get }
    static var FlexibleRightMargin: UIViewAutoresizing { get }
    static var FlexibleTopMargin: UIViewAutoresizing { get }
    static var FlexibleHeight: UIViewAutoresizing { get }
    static var FlexibleBottomMargin: UIViewAutoresizing { get }
}

RawOptionsSetTypeNS_OPTIONS 类型在 Swift 当中的替代品(至少是最接近的东西了)。它是一个协议,遵守 RawRepresentable, Equatable, BitwiseOperationsType, 和 NilLiteralConvertible 这几个协议。一个选项(option)类型可以用一个遵守 RawOptionsSetType 协议的 struct 表示。

为什么这货这么差劲?主要是因为 C 语言中位运算的技巧不能用于 Swift 中的枚举类型。一个 enum 代表着一系列可用选项的封闭集合,但是并没有内建一个用来表示若干选项的交集的机制。表面上,一个 enum 可以定义出选项值所有可能的组合,但是对于 n > 3 的情况,组合数学告诉我们这种办法是不靠谱的。在 Swift 中实现 NS_OPTIONS 有很多种方式,RawOptionSetType 可能还不是最差的。

和语法上清晰而明确的 enum 声明相比,RawOptionsSetType 显得笨拙而冗长,需要一些模板代码来支持计算属性(computed properties):

struct Toppings : RawOptionSetType, BooleanType {
    private var value: UInt = 0

    init(_ value: UInt) {
        self.value = value
    }

    // MARK: RawOptionSetType

    static func fromMask(raw: UInt) -> Toppings {
        return self(raw)
    }

    // MARK: RawRepresentable

    static func fromRaw(raw: UInt) -> Toppings? {
        return self(raw)
    }

    func toRaw() -> UInt {
        return value
    }

    // MARK: BooleanType

    var boolValue: Bool {
        return value != 0
    }


    // MARK: BitwiseOperationsType

    static var allZeros: Toppings {
        return self(0)
    }

    // MARK: NilLiteralConvertible

    static func convertFromNilLiteral() -> Toppings {
        return self(0)
    }

    // MARK: -

    static var None: Toppings           { return self(0b0000) }
    static var ExtraCheese: Toppings    { return self(0b0001) }
    static var Pepperoni: Toppings      { return self(0b0010) }
    static var GreenPepper: Toppings    { return self(0b0100) }
    static var Pineapple: Toppings      { return self(0b1000) }
}

在 Xcode 6 Beta 6 中,RawOptionSetType 不再遵守 BooleanType 协议,如果想支持按位检查的话还是需要支持 BooleanType

在 Swift 中这种写法的一个好处是,Swift 内建的二进制整数字面值支持进行视觉上的按位运算。当 options 类型定义完成之后,使用它的语法还不算特别难看。

以下面这个大一些的例子为例:

struct Pizza {
    enum Style {
        case Neopolitan, Sicilian, NewHaven, DeepDish
    }

    struct Toppings : RawOptionSetType { ... }

    let diameter: Int
    let style: Style
    let toppings: Toppings

    init(inchesInDiameter diameter: Int, style: Style, toppings: Toppings = .None) {
        self.diameter = diameter
        self.style = style
        self.toppings = toppings
    }
}

let dinner = Pizza(inchesInDiameter: 12, style: .Neopolitan, toppings: .Pepperoni | .GreenPepper)

对于值的归属检查,可以使用 & 运算符,就像在 C 中操作无符号整数一样:

extension Pizza {
    var isVegetarian: Bool {
        return toppings & Toppings.Pepperoni ? false : true
    }
}

dinner.isVegetarian // false

平心而论,现在来谈论选项(option)类型在 Swift 语言当中的角色还为时过早。很有可能 Swift 中的其他结构,例如元组(tuple)和模式匹配(pattern matching),也可能就是 enum 本身,会让选项类型变得不仅仅只是来自过去的遗迹。

不管怎样,如果你想在代码中实现类似 NS_OPTIONS 的结构,下面是一段 Xcode snippet,可以帮助你快速上手:

struct Options : RawOptionSetType, BooleanType {
    let rawValue: UInt
    init(nilLiteral: ()) { self.value = 0 }
    init(_ value: UInt = 0) { self.value = value }
    init(rawValue value: UInt) { self.value = value }
    var boolValue: Bool { return value != 0 }
    var rawValue: UInt { return value }
    static var allZeros: Options { return self(0) }

    static var None: Options         { return self(0b0000) }
    static var Option: Options     { return self(0b0001) }
    // ...
}

```

作者 Mattt
Mattt

Mattt (@mattt) is a writer and developer in Portland, Oregon. He is the founder of NSHipster and Flight School, and the creator of several open source libraries, including AFNetworking and Alamofire.

翻译者
Chester Liu

iOS 工程师,一直想成为更好的自己。我的 GithubStackOverflow

下一篇文章

Objective-C 让我们对相等性和唯一性的本质慢慢有了带有哲学色彩的思考。为了解救那些不愿意向论文一样的哲理卑身屈膝的开发者,Swift 为此作出了很多改进。